diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 000000000..20c07870b --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,5 @@ +plugins: + - prettier-plugin-java +useTabs: false +tabWidth: 4 +printWidth: 120 diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/Admin.java b/vane-admin/src/main/java/org/oddlama/vane/admin/Admin.java index d08204a87..3b86fb84f 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/Admin.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/Admin.java @@ -5,20 +5,21 @@ @VaneModule(name = "admin", bstats = 8638, config_version = 2, lang_version = 2, storage_version = 1) public class Admin extends Module { - public Admin() { - // Create components - new org.oddlama.vane.admin.commands.Gamemode(this); - new org.oddlama.vane.admin.commands.SlimeChunk(this); - new org.oddlama.vane.admin.commands.Time(this); - new org.oddlama.vane.admin.commands.Weather(this); - var autostop_group = new AutostopGroup(this); - new AutostopListener(autostop_group); - new org.oddlama.vane.admin.commands.Autostop(autostop_group); + public Admin() { + // Create components + new org.oddlama.vane.admin.commands.Gamemode(this); + new org.oddlama.vane.admin.commands.SlimeChunk(this); + new org.oddlama.vane.admin.commands.Time(this); + new org.oddlama.vane.admin.commands.Weather(this); - new SpawnProtection(this); - new WorldProtection(this); - new HazardProtection(this); - new ChatMessageFormatter(this); - } + var autostop_group = new AutostopGroup(this); + new AutostopListener(autostop_group); + new org.oddlama.vane.admin.commands.Autostop(autostop_group); + + new SpawnProtection(this); + new WorldProtection(this); + new HazardProtection(this); + new ChatMessageFormatter(this); + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopGroup.java b/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopGroup.java index 139e3142b..a83277506 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopGroup.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopGroup.java @@ -1,7 +1,7 @@ package org.oddlama.vane.admin; -import static org.oddlama.vane.util.TimeUtil.format_time; import static org.oddlama.vane.util.Conversions.ms_to_ticks; +import static org.oddlama.vane.util.TimeUtil.format_time; import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitTask; @@ -13,101 +13,98 @@ public class AutostopGroup extends ModuleGroup { - @ConfigLong(def = 20 * 60, min = 0, desc = "Delay in seconds after which to stop the server.") - public long config_delay; - - @LangMessage - public TranslatedMessage lang_aborted; - - @LangMessage - public TranslatedMessage lang_scheduled; - - @LangMessage - public TranslatedMessage lang_status; - - @LangMessage - public TranslatedMessage lang_status_not_scheduled; - - @LangMessage - public TranslatedMessage lang_shutdown; - - // Variables - public BukkitTask task = null; - public long start_time = -1; - public long stop_time = -1; - - public AutostopGroup(Context context) { - super(context, "autostop", "Enable automatic server stop after certain time without online players."); - } - - public long remaining() { - - if (start_time == -1) { - return -1; - } - - return stop_time - System.currentTimeMillis(); - - } - - public void abort() { - abort(null); - } - - public void abort(CommandSender sender) { - if (task == null) { - lang_status_not_scheduled.send(sender); - return; - } - - task.cancel(); - task = null; - start_time = -1; - stop_time = -1; - - lang_aborted.send_and_log(sender); - } - - public void schedule() { - schedule(null); - } - - public void schedule(CommandSender sender) { - schedule(sender, config_delay * 1000); - } - - public void schedule(CommandSender sender, long delay) { - if (task != null) { - abort(sender); - } - - start_time = System.currentTimeMillis(); - stop_time = start_time + delay; - task = - schedule_task( - () -> { - lang_shutdown.send_and_log(null); - get_module().getServer().shutdown(); - }, - ms_to_ticks(delay) - ); - - lang_scheduled.send_and_log(sender, "§b" + format_time(delay)); - } - - public void status(CommandSender sender) { - if (task == null) { - lang_status_not_scheduled.send(sender); - return; - } - - lang_status.send(sender, "§b" + format_time(remaining())); - } - - @Override - public void on_enable() { - if (get_module().getServer().getOnlinePlayers().isEmpty()) { - schedule(); - } - } + @ConfigLong(def = 20 * 60, min = 0, desc = "Delay in seconds after which to stop the server.") + public long config_delay; + + @LangMessage + public TranslatedMessage lang_aborted; + + @LangMessage + public TranslatedMessage lang_scheduled; + + @LangMessage + public TranslatedMessage lang_status; + + @LangMessage + public TranslatedMessage lang_status_not_scheduled; + + @LangMessage + public TranslatedMessage lang_shutdown; + + // Variables + public BukkitTask task = null; + public long start_time = -1; + public long stop_time = -1; + + public AutostopGroup(Context context) { + super(context, "autostop", "Enable automatic server stop after certain time without online players."); + } + + public long remaining() { + if (start_time == -1) { + return -1; + } + + return stop_time - System.currentTimeMillis(); + } + + public void abort() { + abort(null); + } + + public void abort(CommandSender sender) { + if (task == null) { + lang_status_not_scheduled.send(sender); + return; + } + + task.cancel(); + task = null; + start_time = -1; + stop_time = -1; + + lang_aborted.send_and_log(sender); + } + + public void schedule() { + schedule(null); + } + + public void schedule(CommandSender sender) { + schedule(sender, config_delay * 1000); + } + + public void schedule(CommandSender sender, long delay) { + if (task != null) { + abort(sender); + } + + start_time = System.currentTimeMillis(); + stop_time = start_time + delay; + task = schedule_task( + () -> { + lang_shutdown.send_and_log(null); + get_module().getServer().shutdown(); + }, + ms_to_ticks(delay) + ); + + lang_scheduled.send_and_log(sender, "§b" + format_time(delay)); + } + + public void status(CommandSender sender) { + if (task == null) { + lang_status_not_scheduled.send(sender); + return; + } + + lang_status.send(sender, "§b" + format_time(remaining())); + } + + @Override + public void on_enable() { + if (get_module().getServer().getOnlinePlayers().isEmpty()) { + schedule(); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopListener.java b/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopListener.java index 610008572..c8b9106cf 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopListener.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/AutostopListener.java @@ -10,32 +10,32 @@ public class AutostopListener extends Listener { - AutostopGroup autostop; - - public AutostopListener(AutostopGroup context) { - super(context); - this.autostop = context; - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_join(PlayerJoinEvent event) { - autostop.abort(); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_kick(PlayerKickEvent event) { - player_leave(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_quit(PlayerQuitEvent event) { - player_leave(event.getPlayer()); - } - - private void player_leave(final Player player) { - var players = get_module().getServer().getOnlinePlayers(); - if (players.isEmpty() || (players.size() == 1 && players.iterator().next() == player)) { - autostop.schedule(); - } - } + AutostopGroup autostop; + + public AutostopListener(AutostopGroup context) { + super(context); + this.autostop = context; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_join(PlayerJoinEvent event) { + autostop.abort(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_kick(PlayerKickEvent event) { + player_leave(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_quit(PlayerQuitEvent event) { + player_leave(event.getPlayer()); + } + + private void player_leave(final Player player) { + var players = get_module().getServer().getOnlinePlayers(); + if (players.isEmpty() || (players.size() == 1 && players.iterator().next() == player)) { + autostop.schedule(); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/ChatMessageFormatter.java b/vane-admin/src/main/java/org/oddlama/vane/admin/ChatMessageFormatter.java index 23e4b489a..3e18e2e8a 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/ChatMessageFormatter.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/ChatMessageFormatter.java @@ -18,69 +18,68 @@ public class ChatMessageFormatter extends Listener { - @LangMessage - private TranslatedMessage lang_player_chat_format; + @LangMessage + private TranslatedMessage lang_player_chat_format; - @LangMessage - private TranslatedMessage lang_player_join; + @LangMessage + private TranslatedMessage lang_player_join; - @LangMessage - private TranslatedMessage lang_player_kick; + @LangMessage + private TranslatedMessage lang_player_kick; - @LangMessage - private TranslatedMessage lang_player_quit; + @LangMessage + private TranslatedMessage lang_player_quit; - final ChatRenderer chat_renderer; + final ChatRenderer chat_renderer; - public ChatMessageFormatter(Context context) { - super( - context.group( - "chat_message_formatter", - "Enables custom formatting of chat messages like player chats and join / quit messages." - ) - ); - // Create custom chat renderer - chat_renderer = - new ChatRenderer() { - public Component render( - final Player source, - final Component sourceDisplayName, - final Component message, - final Audience viewer - ) { - // TODO more sophisticated formatting? - final var who = sourceDisplayName.color(NamedTextColor.AQUA); - return lang_player_chat_format.str_component(who, message); - } - }; - } + public ChatMessageFormatter(Context context) { + super( + context.group( + "chat_message_formatter", + "Enables custom formatting of chat messages like player chats and join / quit messages." + ) + ); + // Create custom chat renderer + chat_renderer = new ChatRenderer() { + public Component render( + final Player source, + final Component sourceDisplayName, + final Component message, + final Audience viewer + ) { + // TODO more sophisticated formatting? + final var who = sourceDisplayName.color(NamedTextColor.AQUA); + return lang_player_chat_format.str_component(who, message); + } + }; + } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_chat(AsyncChatEvent event) { - event.renderer(chat_renderer); - } + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_chat(AsyncChatEvent event) { + event.renderer(chat_renderer); + } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_join(final PlayerJoinEvent event) { - event.joinMessage(null); - lang_player_join.broadcast_server(event.getPlayer().playerListName().color(NamedTextColor.GOLD)); - } + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_join(final PlayerJoinEvent event) { + event.joinMessage(null); + lang_player_join.broadcast_server(event.getPlayer().playerListName().color(NamedTextColor.GOLD)); + } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_player_kick(final PlayerKickEvent event) { - // Bug in Spigot, doesn't do anything. But fixed in Paper since 1.17. - // https://hub.spigotmc.org/jira/browse/SPIGOT-3034 - event.leaveMessage(Component.text("")); - // message is handled in quit event - } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_player_kick(final PlayerKickEvent event) { + // Bug in Spigot, doesn't do anything. But fixed in Paper since 1.17. + // https://hub.spigotmc.org/jira/browse/SPIGOT-3034 + event.leaveMessage(Component.text("")); + // message is handled in quit event + } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_player_quit(final PlayerQuitEvent event) { - event.quitMessage(null); - if (event.getReason() == PlayerQuitEvent.QuitReason.KICKED) { - lang_player_kick.broadcast_server(event.getPlayer().playerListName().color(NamedTextColor.GOLD)); - } else { - lang_player_quit.broadcast_server(event.getPlayer().playerListName().color(NamedTextColor.GOLD)); - } - } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_player_quit(final PlayerQuitEvent event) { + event.quitMessage(null); + if (event.getReason() == PlayerQuitEvent.QuitReason.KICKED) { + lang_player_kick.broadcast_server(event.getPlayer().playerListName().color(NamedTextColor.GOLD)); + } else { + lang_player_quit.broadcast_server(event.getPlayer().playerListName().color(NamedTextColor.GOLD)); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/HazardProtection.java b/vane-admin/src/main/java/org/oddlama/vane/admin/HazardProtection.java index 29677aecf..b2747305a 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/HazardProtection.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/HazardProtection.java @@ -19,125 +19,124 @@ public class HazardProtection extends Listener { - private WorldRebuild world_rebuild; - - @ConfigBoolean(def = true, desc = "Restrict wither spawning to a list of worlds defined by wither_world_whitelist.") - private boolean config_enable_wither_world_whitelist; - - @ConfigStringList( - def = { "world_nether", "world_the_end" }, - desc = "A list of worlds in which the wither may be spawned." - ) - private List config_wither_world_whitelist; - - @ConfigBoolean(def = true, desc = "Disables explosions from the wither.") - private boolean config_disable_wither_explosions; - - @ConfigBoolean(def = true, desc = "Disables explosions from creepers.") - private boolean config_disable_creeper_explosions; - - @ConfigBoolean(def = true, desc = "Disables enderman block pickup.") - private boolean config_disable_enderman_block_pickup; - - @ConfigBoolean(def = true, desc = "Disables entities from breaking doors (various zombies).") - private boolean config_disable_door_breaking; - - @ConfigBoolean(def = true, desc = "Disables fire from lightning.") - private boolean config_disable_lightning_fire; - - @LangMessage - private TranslatedMessage lang_wither_spawn_prohibited; - - public HazardProtection(Context context) { - super( - context.group( - "hazard_protection", - "Enable hazard protection. The options below allow more fine-grained control over the hazards to protect from." - ) - ); - world_rebuild = new WorldRebuild(get_context()); - } - - private boolean disable_explosion(EntityType type) { - switch (type) { - default: - return false; - case WITHER: - case WITHER_SKULL: - return config_disable_wither_explosions; - case CREEPER: - return config_disable_creeper_explosions; - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_entity_explode(final EntityExplodeEvent event) { - if (disable_explosion(event.getEntityType())) { - if (world_rebuild.enabled()) { - // Schedule rebuild - world_rebuild.rebuild(event.blockList()); - // Remove all affected blocks from event - event.blockList().clear(); - } else { - event.setCancelled(true); - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_hanging_break_by_entity(final HangingBreakByEntityEvent event) { - if (disable_explosion(event.getRemover().getType())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_entity_break_door(final EntityBreakDoorEvent event) { - if (config_disable_door_breaking) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_block_ignite(final BlockIgniteEvent event) { - if (event.getCause() == BlockIgniteEvent.IgniteCause.LIGHTNING - && config_disable_lightning_fire) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_creature_spawn(final CreatureSpawnEvent event) { - if (!config_enable_wither_world_whitelist) { - return; - } - - // Only for wither spawns - if (event.getEntity().getType() != EntityType.WITHER) { - return; - } - - // Check if the world is whitelisted - final var world = event.getEntity().getWorld(); - if (config_wither_world_whitelist.contains(world.getName())) { - return; - } - - lang_wither_spawn_prohibited.broadcast_world(world, world.getName()); - event.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_entity_block_change(final EntityChangeBlockEvent event) { - if (!config_disable_enderman_block_pickup) { - return; - } - - // Only for enderman events - if (event.getEntity().getType() != EntityType.ENDERMAN) { - return; - } - - event.setCancelled(true); - } + private WorldRebuild world_rebuild; + + @ConfigBoolean(def = true, desc = "Restrict wither spawning to a list of worlds defined by wither_world_whitelist.") + private boolean config_enable_wither_world_whitelist; + + @ConfigStringList( + def = { "world_nether", "world_the_end" }, + desc = "A list of worlds in which the wither may be spawned." + ) + private List config_wither_world_whitelist; + + @ConfigBoolean(def = true, desc = "Disables explosions from the wither.") + private boolean config_disable_wither_explosions; + + @ConfigBoolean(def = true, desc = "Disables explosions from creepers.") + private boolean config_disable_creeper_explosions; + + @ConfigBoolean(def = true, desc = "Disables enderman block pickup.") + private boolean config_disable_enderman_block_pickup; + + @ConfigBoolean(def = true, desc = "Disables entities from breaking doors (various zombies).") + private boolean config_disable_door_breaking; + + @ConfigBoolean(def = true, desc = "Disables fire from lightning.") + private boolean config_disable_lightning_fire; + + @LangMessage + private TranslatedMessage lang_wither_spawn_prohibited; + + public HazardProtection(Context context) { + super( + context.group( + "hazard_protection", + "Enable hazard protection. The options below allow more fine-grained control over the hazards to protect from." + ) + ); + world_rebuild = new WorldRebuild(get_context()); + } + + private boolean disable_explosion(EntityType type) { + switch (type) { + default: + return false; + case WITHER: + case WITHER_SKULL: + return config_disable_wither_explosions; + case CREEPER: + return config_disable_creeper_explosions; + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_entity_explode(final EntityExplodeEvent event) { + if (disable_explosion(event.getEntityType())) { + if (world_rebuild.enabled()) { + // Schedule rebuild + world_rebuild.rebuild(event.blockList()); + // Remove all affected blocks from event + event.blockList().clear(); + } else { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_hanging_break_by_entity(final HangingBreakByEntityEvent event) { + if (disable_explosion(event.getRemover().getType())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_entity_break_door(final EntityBreakDoorEvent event) { + if (config_disable_door_breaking) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_block_ignite(final BlockIgniteEvent event) { + if (event.getCause() == BlockIgniteEvent.IgniteCause.LIGHTNING && config_disable_lightning_fire) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_creature_spawn(final CreatureSpawnEvent event) { + if (!config_enable_wither_world_whitelist) { + return; + } + + // Only for wither spawns + if (event.getEntity().getType() != EntityType.WITHER) { + return; + } + + // Check if the world is whitelisted + final var world = event.getEntity().getWorld(); + if (config_wither_world_whitelist.contains(world.getName())) { + return; + } + + lang_wither_spawn_prohibited.broadcast_world(world, world.getName()); + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_entity_block_change(final EntityChangeBlockEvent event) { + if (!config_disable_enderman_block_pickup) { + return; + } + + // Only for enderman events + if (event.getEntity().getType() != EntityType.ENDERMAN) { + return; + } + + event.setCancelled(true); + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/SpawnProtection.java b/vane-admin/src/main/java/org/oddlama/vane/admin/SpawnProtection.java index 75b1f0726..f8e57b570 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/SpawnProtection.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/SpawnProtection.java @@ -25,161 +25,161 @@ public class SpawnProtection extends Listener { - private static final String PERMISSION_NAME = "vane.admin.bypass_spawn_protection"; - private Permission permission = new Permission( - PERMISSION_NAME, - "Allow player to bypass spawn protection", - PermissionDefault.OP - ); - - @ConfigBoolean(def = true, desc = "Allow interaction events at spawn (buttons, levers, etc.).") - private boolean config_allow_interaction; - - @ConfigInt(def = 64, min = 0, desc = "Radius to protect.") - private int config_radius; - - @ConfigString(def = "world", desc = "The spawn world.") - private String config_world; - - @ConfigBoolean(def = true, desc = "Use world's spawn location instead of the specified center coordinates.") - private boolean config_use_spawn_location; - - @ConfigInt(def = 0, desc = "Center X coordinate.") - private int config_x; - - @ConfigInt(def = 0, desc = "Center Z coordinate.") - private int config_z; - - public SpawnProtection(Context context) { - super( - context.group_default_disabled( - "spawn_protection", - "Enable spawn protection. Slightly more sophisticated than the vanilla spawn protection, if you need even more control, use regions. This will prevent anyone from modifying the spawn of the world if they don't have the permission '" + - PERMISSION_NAME + - "'." - ) - ); - get_module().register_permission(permission); - } - - private Location spawn_center = null; - - @Override - public void on_config_change() { - spawn_center = null; - schedule_next_tick(() -> { - final var world = get_module().getServer().getWorld(config_world); - if (world == null) { - //todo print error and show valid worlds. - get_module() - .log.warning( - "The world \"" + config_world + "\" configured for spawn-protection could not be found." - ); - get_module().log.warning("These are the names of worlds existing on this server:"); - for (final var w : get_module().getServer().getWorlds()) { - get_module().log.warning(" \"" + w.getName() + "\""); - } - spawn_center = null; - } else { - if (config_use_spawn_location) { - spawn_center = world.getSpawnLocation(); - spawn_center.setY(0); - } else { - spawn_center = new Location(world, config_x, 0, config_z); - } - } - }); - } - - public boolean deny_modify_spawn(final Block block, final Entity entity) { - return deny_modify_spawn(block.getLocation(), entity); - } - - public boolean deny_modify_spawn(final Location location, final Entity entity) { - if (spawn_center == null || !(entity instanceof Player)) { - return false; - } - - final var dx = location.getX() - spawn_center.getX(); - final var dz = location.getZ() - spawn_center.getZ(); - final var distance = Math.sqrt(dx * dx + dz * dz); - if (distance > config_radius) { - return false; - } - - return !entity.hasPermission(permission); - } - - /* ************************ blocks ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_block_break(BlockBreakEvent event) { - if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_block_place(BlockPlaceEvent event) { - if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { - event.setCancelled(true); - } - } - - /* ************************ hanging ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_hanging_break_by_entity(HangingBreakByEntityEvent event) { - if (deny_modify_spawn(event.getEntity().getLocation(), event.getRemover())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_hanging_place(HangingPlaceEvent event) { - if (deny_modify_spawn(event.getEntity().getLocation(), event.getPlayer())) { - event.setCancelled(true); - } - } - - /* ************************ player ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_armor_stand_manipulate(PlayerArmorStandManipulateEvent event) { - if (deny_modify_spawn(event.getRightClicked().getLocation(), event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_bucket_empty(PlayerBucketEmptyEvent event) { - if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_bucket_fill(PlayerBucketFillEvent event) { - if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_interact_entity(PlayerInteractEntityEvent event) { - if (!config_allow_interaction && deny_modify_spawn(event.getRightClicked().getLocation(), event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_interact(PlayerInteractEvent event) { - if ( - event.getClickedBlock() != null && - !config_allow_interaction && - deny_modify_spawn(event.getClickedBlock(), event.getPlayer()) - ) { - event.setCancelled(true); - } - } + private static final String PERMISSION_NAME = "vane.admin.bypass_spawn_protection"; + private Permission permission = new Permission( + PERMISSION_NAME, + "Allow player to bypass spawn protection", + PermissionDefault.OP + ); + + @ConfigBoolean(def = true, desc = "Allow interaction events at spawn (buttons, levers, etc.).") + private boolean config_allow_interaction; + + @ConfigInt(def = 64, min = 0, desc = "Radius to protect.") + private int config_radius; + + @ConfigString(def = "world", desc = "The spawn world.") + private String config_world; + + @ConfigBoolean(def = true, desc = "Use world's spawn location instead of the specified center coordinates.") + private boolean config_use_spawn_location; + + @ConfigInt(def = 0, desc = "Center X coordinate.") + private int config_x; + + @ConfigInt(def = 0, desc = "Center Z coordinate.") + private int config_z; + + public SpawnProtection(Context context) { + super( + context.group_default_disabled( + "spawn_protection", + "Enable spawn protection. Slightly more sophisticated than the vanilla spawn protection, if you need even more control, use regions. This will prevent anyone from modifying the spawn of the world if they don't have the permission '" + + PERMISSION_NAME + + "'." + ) + ); + get_module().register_permission(permission); + } + + private Location spawn_center = null; + + @Override + public void on_config_change() { + spawn_center = null; + schedule_next_tick(() -> { + final var world = get_module().getServer().getWorld(config_world); + if (world == null) { + // todo print error and show valid worlds. + get_module() + .log.warning( + "The world \"" + config_world + "\" configured for spawn-protection could not be found." + ); + get_module().log.warning("These are the names of worlds existing on this server:"); + for (final var w : get_module().getServer().getWorlds()) { + get_module().log.warning(" \"" + w.getName() + "\""); + } + spawn_center = null; + } else { + if (config_use_spawn_location) { + spawn_center = world.getSpawnLocation(); + spawn_center.setY(0); + } else { + spawn_center = new Location(world, config_x, 0, config_z); + } + } + }); + } + + public boolean deny_modify_spawn(final Block block, final Entity entity) { + return deny_modify_spawn(block.getLocation(), entity); + } + + public boolean deny_modify_spawn(final Location location, final Entity entity) { + if (spawn_center == null || !(entity instanceof Player)) { + return false; + } + + final var dx = location.getX() - spawn_center.getX(); + final var dz = location.getZ() - spawn_center.getZ(); + final var distance = Math.sqrt(dx * dx + dz * dz); + if (distance > config_radius) { + return false; + } + + return !entity.hasPermission(permission); + } + + /* ************************ blocks ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_block_break(BlockBreakEvent event) { + if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_block_place(BlockPlaceEvent event) { + if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { + event.setCancelled(true); + } + } + + /* ************************ hanging ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_hanging_break_by_entity(HangingBreakByEntityEvent event) { + if (deny_modify_spawn(event.getEntity().getLocation(), event.getRemover())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_hanging_place(HangingPlaceEvent event) { + if (deny_modify_spawn(event.getEntity().getLocation(), event.getPlayer())) { + event.setCancelled(true); + } + } + + /* ************************ player ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_armor_stand_manipulate(PlayerArmorStandManipulateEvent event) { + if (deny_modify_spawn(event.getRightClicked().getLocation(), event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_bucket_empty(PlayerBucketEmptyEvent event) { + if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_bucket_fill(PlayerBucketFillEvent event) { + if (deny_modify_spawn(event.getBlock(), event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_interact_entity(PlayerInteractEntityEvent event) { + if (!config_allow_interaction && deny_modify_spawn(event.getRightClicked().getLocation(), event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_interact(PlayerInteractEvent event) { + if ( + event.getClickedBlock() != null && + !config_allow_interaction && + deny_modify_spawn(event.getClickedBlock(), event.getPlayer()) + ) { + event.setCancelled(true); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/WorldProtection.java b/vane-admin/src/main/java/org/oddlama/vane/admin/WorldProtection.java index f3341e66e..f30a58074 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/WorldProtection.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/WorldProtection.java @@ -29,168 +29,169 @@ public class WorldProtection extends Listener { - private static final String PERMISSION_NAME = "vane.admin.modify_world"; - private Permission permission = new Permission( - PERMISSION_NAME, - "Allow player to modify world", - PermissionDefault.OP - ); - - public WorldProtection(Context context) { - super( - context.group( - "world_protection", - "Enable world protection. This will prevent anyone from modifying the world if they don't have the permission '" + - PERMISSION_NAME + - "'.", false - ) - ); - get_module().register_permission(permission); - } - - public boolean deny_modify_world(final Entity entity) { - if (!(entity instanceof Player)) { - return false; - } - - return !entity.hasPermission(permission); - } - - /* ************************ blocks ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_block_break(BlockBreakEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_block_place(BlockPlaceEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - /* ************************ enchantment ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_prepare_item_enchant(PrepareItemEnchantEvent event) { - if (deny_modify_world(event.getEnchanter())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_item_enchant(EnchantItemEvent event) { - if (deny_modify_world(event.getEnchanter())) { - event.setCancelled(true); - } - } - - /* ************************ entity ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_entity_combust_by_entity(EntityCombustByEntityEvent event) { - if (deny_modify_world(event.getCombuster())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_entity_damage(EntityDamageEvent event) { - if (event instanceof EntityDamageByEntityEvent) { - final var damage_event = (EntityDamageByEntityEvent) event; - if (deny_modify_world(damage_event.getDamager())) { - event.setCancelled(true); - } else if (deny_modify_world(damage_event.getEntity())) { - event.setCancelled(true); - } - } else if (deny_modify_world(event.getEntity())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_entity_food_level_change(FoodLevelChangeEvent event) { - if (deny_modify_world(event.getEntity())) { - event.setCancelled(true); - } - } - - /* ************************ hanging ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_hanging_break_by_entity(HangingBreakByEntityEvent event) { - if (deny_modify_world(event.getRemover())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_hanging_place(HangingPlaceEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - /* ************************ inventory ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_item_craft(CraftItemEvent event) { - if (deny_modify_world(event.getWhoClicked())) { - event.setCancelled(true); - } - } - - /* ************************ player ************************ */ - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_armor_stand_manipulate(PlayerArmorStandManipulateEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_bucket_empty(PlayerBucketEmptyEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_bucket_fill(PlayerBucketFillEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_edit_book(PlayerEditBookEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_interact_entity(PlayerInteractEntityEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_interact(PlayerInteractEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_shear_entity(PlayerShearEntityEvent event) { - if (deny_modify_world(event.getPlayer())) { - event.setCancelled(true); - } - } + private static final String PERMISSION_NAME = "vane.admin.modify_world"; + private Permission permission = new Permission( + PERMISSION_NAME, + "Allow player to modify world", + PermissionDefault.OP + ); + + public WorldProtection(Context context) { + super( + context.group( + "world_protection", + "Enable world protection. This will prevent anyone from modifying the world if they don't have the permission '" + + PERMISSION_NAME + + "'.", + false + ) + ); + get_module().register_permission(permission); + } + + public boolean deny_modify_world(final Entity entity) { + if (!(entity instanceof Player)) { + return false; + } + + return !entity.hasPermission(permission); + } + + /* ************************ blocks ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_block_break(BlockBreakEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_block_place(BlockPlaceEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + /* ************************ enchantment ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_prepare_item_enchant(PrepareItemEnchantEvent event) { + if (deny_modify_world(event.getEnchanter())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_item_enchant(EnchantItemEvent event) { + if (deny_modify_world(event.getEnchanter())) { + event.setCancelled(true); + } + } + + /* ************************ entity ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_entity_combust_by_entity(EntityCombustByEntityEvent event) { + if (deny_modify_world(event.getCombuster())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_entity_damage(EntityDamageEvent event) { + if (event instanceof EntityDamageByEntityEvent) { + final var damage_event = (EntityDamageByEntityEvent) event; + if (deny_modify_world(damage_event.getDamager())) { + event.setCancelled(true); + } else if (deny_modify_world(damage_event.getEntity())) { + event.setCancelled(true); + } + } else if (deny_modify_world(event.getEntity())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_entity_food_level_change(FoodLevelChangeEvent event) { + if (deny_modify_world(event.getEntity())) { + event.setCancelled(true); + } + } + + /* ************************ hanging ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_hanging_break_by_entity(HangingBreakByEntityEvent event) { + if (deny_modify_world(event.getRemover())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_hanging_place(HangingPlaceEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + /* ************************ inventory ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_item_craft(CraftItemEvent event) { + if (deny_modify_world(event.getWhoClicked())) { + event.setCancelled(true); + } + } + + /* ************************ player ************************ */ + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_armor_stand_manipulate(PlayerArmorStandManipulateEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_bucket_empty(PlayerBucketEmptyEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_bucket_fill(PlayerBucketFillEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_edit_book(PlayerEditBookEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_interact_entity(PlayerInteractEntityEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_interact(PlayerInteractEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_shear_entity(PlayerShearEntityEvent event) { + if (deny_modify_world(event.getPlayer())) { + event.setCancelled(true); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/WorldRebuild.java b/vane-admin/src/main/java/org/oddlama/vane/admin/WorldRebuild.java index 042b208b5..5b144b4a7 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/WorldRebuild.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/WorldRebuild.java @@ -1,7 +1,7 @@ package org.oddlama.vane.admin; -import static org.oddlama.vane.util.Nms.set_air_no_drops; import static org.oddlama.vane.util.Conversions.ms_to_ticks; +import static org.oddlama.vane.util.Nms.set_air_no_drops; import java.util.ArrayList; import java.util.Comparator; @@ -19,166 +19,166 @@ public class WorldRebuild extends Listener { - @ConfigLong(def = 2000, min = 0, desc = "Delay in milliseconds until the world will be rebuilt.") - private long config_delay; - - @ConfigDouble( - def = 0.175, - min = 0.0, - desc = "Determines rebuild speed. Higher falloff means faster transition to quicker rebuild. After n blocks, the delay until the next block will be d_n = delay * exp(-x * delay_falloff). For example 0.0 will result in same delay for every block." - ) - private double config_delay_falloff; - - @ConfigLong( - def = 50, - min = 50, - desc = "Minimum delay in milliseconds between rebuilding two blocks. Anything <= 50 milliseconds will be one tick." - ) - private long config_min_delay; - - public WorldRebuild(Context context) { - super( - context.group( - "world_rebuild", - "Instead of cancelling explosions, the world will regenerate after a short amount of time." - ) - ); - } - - private final List rebuilders = new ArrayList<>(); - - public void rebuild(final List blocks) { - // Store a snapshot of all block states - final var states = new ArrayList(); - for (final var block : blocks) { - states.add(block.getState()); - } - - // Set everything to air without triggering physics - for (final var block : blocks) { - set_air_no_drops(block); - } - - // Schedule rebuild - rebuilders.add(new Rebuilder(states)); - } - - @Override - public void on_disable() { - // Finish all pending rebuilds now! - for (final var r : new ArrayList<>(rebuilders)) { - r.finish_now(); - } - rebuilders.clear(); - } - - public class Rebuilder implements Runnable { - - private List states; - private BukkitTask task = null; - private long amount_rebuild = 0; - - public Rebuilder(final List _states) { - this.states = _states; - if (this.states.isEmpty()) { - return; - } - - // Find top center point for rebuild order reference - Vector center = new Vector(0, 0, 0); - int max_y = 0; - for (final var state : this.states) { - max_y = Math.max(max_y, state.getY()); - center.add(state.getLocation().toVector()); - } - center.multiply(1.0 / this.states.size()); - center.setY(max_y + 1); - - // Sort blocks to rebuild them in an ordered fashion - this.states.sort(new RebuildComparator(center)); - - // Initialize delay - task = get_module().schedule_task(this, ms_to_ticks(config_delay)); - } - - private void finish() { - task = null; - WorldRebuild.this.rebuilders.remove(this); - } - - private void rebuild_next_block() { - rebuild_block(states.remove(states.size() - 1)); - } - - private void rebuild_block(final BlockState state) { - final var block = state.getBlock(); - ++amount_rebuild; - - // Break any block that isn't air first - if (block.getType() != Material.AIR) { - block.breakNaturally(); - } - - // Force update without physics to set a block type - state.update(true, false); - // Second update forces block state specific update - state.update(true, false); - - // Play sound - block - .getWorld() - .playSound( - block.getLocation(), - block.getBlockSoundGroup().getPlaceSound(), - SoundCategory.BLOCKS, - 1.0f, - 0.8f - ); - } - - public void finish_now() { - if (task != null) { - task.cancel(); - } - - for (final var state : states) { - rebuild_block(state); - } - - finish(); - } - - @Override - public void run() { - if (states.isEmpty()) { - finish(); - } else { - // Rebuild next block - rebuild_next_block(); - - // Adjust delay - final var delay = ms_to_ticks( - Math.max(config_min_delay, (int) (config_delay * Math.exp(-amount_rebuild * config_delay_falloff))) - ); - WorldRebuild.this.get_module().schedule_task(this, delay); - } - } - } - - public static class RebuildComparator implements Comparator { - - private Vector reference_point; - - public RebuildComparator(final Vector reference_point) { - this.reference_point = reference_point; - } - - @Override - public int compare(final BlockState a, final BlockState b) { - // Sort by distance to top-most center. The Last block will be rebuilt first. - final var da = a.getLocation().toVector().subtract(reference_point).lengthSquared(); - final var db = b.getLocation().toVector().subtract(reference_point).lengthSquared(); - return Double.compare(da, db); - } - } + @ConfigLong(def = 2000, min = 0, desc = "Delay in milliseconds until the world will be rebuilt.") + private long config_delay; + + @ConfigDouble( + def = 0.175, + min = 0.0, + desc = "Determines rebuild speed. Higher falloff means faster transition to quicker rebuild. After n blocks, the delay until the next block will be d_n = delay * exp(-x * delay_falloff). For example 0.0 will result in same delay for every block." + ) + private double config_delay_falloff; + + @ConfigLong( + def = 50, + min = 50, + desc = "Minimum delay in milliseconds between rebuilding two blocks. Anything <= 50 milliseconds will be one tick." + ) + private long config_min_delay; + + public WorldRebuild(Context context) { + super( + context.group( + "world_rebuild", + "Instead of cancelling explosions, the world will regenerate after a short amount of time." + ) + ); + } + + private final List rebuilders = new ArrayList<>(); + + public void rebuild(final List blocks) { + // Store a snapshot of all block states + final var states = new ArrayList(); + for (final var block : blocks) { + states.add(block.getState()); + } + + // Set everything to air without triggering physics + for (final var block : blocks) { + set_air_no_drops(block); + } + + // Schedule rebuild + rebuilders.add(new Rebuilder(states)); + } + + @Override + public void on_disable() { + // Finish all pending rebuilds now! + for (final var r : new ArrayList<>(rebuilders)) { + r.finish_now(); + } + rebuilders.clear(); + } + + public class Rebuilder implements Runnable { + + private List states; + private BukkitTask task = null; + private long amount_rebuild = 0; + + public Rebuilder(final List _states) { + this.states = _states; + if (this.states.isEmpty()) { + return; + } + + // Find top center point for rebuild order reference + Vector center = new Vector(0, 0, 0); + int max_y = 0; + for (final var state : this.states) { + max_y = Math.max(max_y, state.getY()); + center.add(state.getLocation().toVector()); + } + center.multiply(1.0 / this.states.size()); + center.setY(max_y + 1); + + // Sort blocks to rebuild them in an ordered fashion + this.states.sort(new RebuildComparator(center)); + + // Initialize delay + task = get_module().schedule_task(this, ms_to_ticks(config_delay)); + } + + private void finish() { + task = null; + WorldRebuild.this.rebuilders.remove(this); + } + + private void rebuild_next_block() { + rebuild_block(states.remove(states.size() - 1)); + } + + private void rebuild_block(final BlockState state) { + final var block = state.getBlock(); + ++amount_rebuild; + + // Break any block that isn't air first + if (block.getType() != Material.AIR) { + block.breakNaturally(); + } + + // Force update without physics to set a block type + state.update(true, false); + // Second update forces block state specific update + state.update(true, false); + + // Play sound + block + .getWorld() + .playSound( + block.getLocation(), + block.getBlockSoundGroup().getPlaceSound(), + SoundCategory.BLOCKS, + 1.0f, + 0.8f + ); + } + + public void finish_now() { + if (task != null) { + task.cancel(); + } + + for (final var state : states) { + rebuild_block(state); + } + + finish(); + } + + @Override + public void run() { + if (states.isEmpty()) { + finish(); + } else { + // Rebuild next block + rebuild_next_block(); + + // Adjust delay + final var delay = ms_to_ticks( + Math.max(config_min_delay, (int) (config_delay * Math.exp(-amount_rebuild * config_delay_falloff))) + ); + WorldRebuild.this.get_module().schedule_task(this, delay); + } + } + } + + public static class RebuildComparator implements Comparator { + + private Vector reference_point; + + public RebuildComparator(final Vector reference_point) { + this.reference_point = reference_point; + } + + @Override + public int compare(final BlockState a, final BlockState b) { + // Sort by distance to top-most center. The Last block will be rebuilt first. + final var da = a.getLocation().toVector().subtract(reference_point).lengthSquared(); + final var db = b.getLocation().toVector().subtract(reference_point).lengthSquared(); + return Double.compare(da, db); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Autostop.java b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Autostop.java index 9046edd6b..258daf4c3 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Autostop.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Autostop.java @@ -4,6 +4,9 @@ import static io.papermc.paper.command.brigadier.Commands.argument; import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import org.bukkit.command.CommandSender; import org.oddlama.vane.admin.Admin; import org.oddlama.vane.admin.AutostopGroup; @@ -11,48 +14,64 @@ import org.oddlama.vane.core.command.Command; import org.oddlama.vane.util.Conversions; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; - -import io.papermc.paper.command.brigadier.CommandSourceStack; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; - @Name("autostop") public class Autostop extends Command { - AutostopGroup autostop; - - public Autostop(AutostopGroup context) { - super(context); - this.autostop = context; - } - - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .executes(ctx -> { status(ctx.getSource().getSender()); return SINGLE_SUCCESS; }) - .then(help()) - .then(literal("status").executes(ctx -> { status(ctx.getSource().getSender()); return SINGLE_SUCCESS; })) - .then(literal("abort").executes(ctx -> { abort(ctx.getSource().getSender()); return SINGLE_SUCCESS;})) - .then(literal("schedule") - .executes(ctx -> { schedule(ctx.getSource().getSender()); return SINGLE_SUCCESS; }) - .then(argument("time", ArgumentTypes.time()) - .executes(ctx -> { schedule_delay(ctx.getSource().getSender(), ctx.getArgument("time", Integer.class)); return SINGLE_SUCCESS;})) - ); - } - - private void status(CommandSender sender) { - autostop.status(sender); - } - - private void abort(CommandSender sender) { - autostop.abort(sender); - } - - private void schedule(CommandSender sender) { - autostop.schedule(sender); - } - - private void schedule_delay(CommandSender sender, int delay){ - autostop.schedule(sender, Conversions.ticks_to_ms(delay)); - } + AutostopGroup autostop; + + public Autostop(AutostopGroup context) { + super(context); + this.autostop = context; + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .executes(ctx -> { + status(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + .then(help()) + .then( + literal("status").executes(ctx -> { + status(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + ) + .then( + literal("abort").executes(ctx -> { + abort(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + ) + .then( + literal("schedule") + .executes(ctx -> { + schedule(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + .then( + argument("time", ArgumentTypes.time()).executes(ctx -> { + schedule_delay(ctx.getSource().getSender(), ctx.getArgument("time", Integer.class)); + return SINGLE_SUCCESS; + }) + ) + ); + } + + private void status(CommandSender sender) { + autostop.status(sender); + } + + private void abort(CommandSender sender) { + autostop.abort(sender); + } + + private void schedule(CommandSender sender) { + autostop.schedule(sender); + } + + private void schedule_delay(CommandSender sender, int delay) { + autostop.schedule(sender, Conversions.ticks_to_ms(delay)); + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Gamemode.java b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Gamemode.java index fa1924aaa..362a10d2b 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Gamemode.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Gamemode.java @@ -2,8 +2,15 @@ import static com.mojang.brigadier.Command.SINGLE_SUCCESS; import static io.papermc.paper.command.brigadier.Commands.argument; -import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.GameMode; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -15,70 +22,76 @@ import org.oddlama.vane.core.lang.TranslatedMessage; import org.oddlama.vane.core.module.Context; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; - -import io.papermc.paper.command.brigadier.CommandSourceStack; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; -import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; - @Name("gamemode") @Aliases({ "gm" }) public class Gamemode extends Command { - @LangMessage - private TranslatedMessage lang_set; + @LangMessage + private TranslatedMessage lang_set; - public Gamemode(Context context) { - super(context); - } + public Gamemode(Context context) { + super(context); + } - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .then(help()) + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .then(help()) + .executes(ctx -> { + toggle_gamemode_self((Player) ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + .then( + argument("game_mode", ArgumentTypes.gameMode()) + .executes(ctx -> { + set_gamemode_self( + (Player) ctx.getSource().getSender(), + ctx.getArgument("game_mode", GameMode.class) + ); + return SINGLE_SUCCESS; + }) + .then( + argument("player", ArgumentTypes.player()).executes(ctx -> { + set_gamemode( + ctx.getSource().getSender(), + ctx.getArgument("game_mode", GameMode.class), + player(ctx) + ); + return SINGLE_SUCCESS; + }) + ) + ) + .then( + argument("player", ArgumentTypes.player()).executes(ctx -> { + toggle_gamemode_player(ctx.getSource().getSender(), player(ctx)); - .executes(ctx -> {toggle_gamemode_self((Player) ctx.getSource().getSender()); return SINGLE_SUCCESS;}) - - .then(argument("game_mode", ArgumentTypes.gameMode()) - .executes(ctx -> { set_gamemode_self((Player) ctx.getSource().getSender(), ctx.getArgument("game_mode", GameMode.class)); return SINGLE_SUCCESS;}) - .then(argument("player", ArgumentTypes.player()) - .executes(ctx -> { - set_gamemode(ctx.getSource().getSender(), ctx.getArgument("game_mode", GameMode.class), player(ctx)); - return SINGLE_SUCCESS; - }) - ) - ) - .then(argument("player", ArgumentTypes.player()) - .executes(ctx -> { - toggle_gamemode_player(ctx.getSource().getSender(), player(ctx)); - - return SINGLE_SUCCESS; - }) - ); - } + return SINGLE_SUCCESS; + }) + ); + } - private Player player(CommandContext ctx) throws CommandSyntaxException{ - return ctx.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).get(0); - } + private Player player(CommandContext ctx) throws CommandSyntaxException { + return ctx.getArgument("player", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).get(0); + } - private void toggle_gamemode_self(Player player) { - toggle_gamemode_player(player, player); - } + private void toggle_gamemode_self(Player player) { + toggle_gamemode_player(player, player); + } - private void toggle_gamemode_player(CommandSender sender, Player player) { - set_gamemode(sender, player.getGameMode() == GameMode.CREATIVE ? GameMode.SURVIVAL : GameMode.CREATIVE, player); - } + private void toggle_gamemode_player(CommandSender sender, Player player) { + set_gamemode(sender, player.getGameMode() == GameMode.CREATIVE ? GameMode.SURVIVAL : GameMode.CREATIVE, player); + } - private void set_gamemode_self(Player player, GameMode mode) { - set_gamemode(player, mode, player); - } + private void set_gamemode_self(Player player, GameMode mode) { + set_gamemode(player, mode, player); + } - private void set_gamemode(CommandSender sender, GameMode mode, Player player) { - player.setGameMode(mode); - lang_set.send(sender, player.displayName().color(NamedTextColor.AQUA), Component.text(mode.name(), NamedTextColor.GREEN)); - } + private void set_gamemode(CommandSender sender, GameMode mode, Player player) { + player.setGameMode(mode); + lang_set.send( + sender, + player.displayName().color(NamedTextColor.AQUA), + Component.text(mode.name(), NamedTextColor.GREEN) + ); + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/SlimeChunk.java b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/SlimeChunk.java index aadcf7dac..0b266e17f 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/SlimeChunk.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/SlimeChunk.java @@ -1,8 +1,9 @@ package org.oddlama.vane.admin.commands; import static com.mojang.brigadier.Command.SINGLE_SUCCESS; -import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.entity.Player; import org.oddlama.vane.admin.Admin; import org.oddlama.vane.annotation.command.Name; @@ -11,36 +12,35 @@ import org.oddlama.vane.core.lang.TranslatedMessage; import org.oddlama.vane.core.module.Context; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; - -import io.papermc.paper.command.brigadier.CommandSourceStack; - @Name("slimechunk") public class SlimeChunk extends Command { - @LangMessage - private TranslatedMessage lang_slime_chunk_yes; - - @LangMessage - private TranslatedMessage lang_slime_chunk_no; - - public SlimeChunk(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .requires(stack -> stack.getSender() instanceof Player) - .then(help()) - .executes(ctx -> {is_slimechunk((Player) ctx.getSource().getSender()); return SINGLE_SUCCESS;}); - } - - private void is_slimechunk(final Player player) { - if (player.getLocation().getChunk().isSlimeChunk()) { - lang_slime_chunk_yes.send(player); - } else { - lang_slime_chunk_no.send(player); - } - } + @LangMessage + private TranslatedMessage lang_slime_chunk_yes; + + @LangMessage + private TranslatedMessage lang_slime_chunk_no; + + public SlimeChunk(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .requires(stack -> stack.getSender() instanceof Player) + .then(help()) + .executes(ctx -> { + is_slimechunk((Player) ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }); + } + + private void is_slimechunk(final Player player) { + if (player.getLocation().getChunk().isSlimeChunk()) { + lang_slime_chunk_yes.send(player); + } else { + lang_slime_chunk_no.send(player); + } + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Time.java b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Time.java index d4cd7826d..c96abb67d 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Time.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Time.java @@ -4,6 +4,10 @@ import static io.papermc.paper.command.brigadier.Commands.argument; import static org.oddlama.vane.util.WorldUtil.change_time_smoothly; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import org.bukkit.World; import org.bukkit.entity.Player; import org.oddlama.vane.admin.Admin; @@ -13,41 +17,41 @@ import org.oddlama.vane.core.command.enums.TimeValue; import org.oddlama.vane.core.module.Context; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; - -import io.papermc.paper.command.brigadier.CommandSourceStack; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; - @Name("time") public class Time extends Command { - public Time(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .then(help()) - .then(argument("time", TimeValueArgumentType.timeValue()) - .executes(ctx -> { set_time_current_world((Player) ctx.getSource().getSender(), time_value(ctx)); return SINGLE_SUCCESS;}) - .then(argument("world", ArgumentTypes.world()) - .executes(ctx -> { set_time(time_value(ctx), ctx.getArgument("world", World.class)); return SINGLE_SUCCESS;}) - ) - ) - ; - } - - private TimeValue time_value(CommandContext ctx) { - return ctx.getArgument("time", TimeValue.class); - } - - private void set_time_current_world(Player player, TimeValue t) { - change_time_smoothly(player.getWorld(), get_module(), t.ticks(), 100); - } - - private void set_time(TimeValue t, World world) { - change_time_smoothly(world, get_module(), t.ticks(), 100); - } + public Time(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .then(help()) + .then( + argument("time", TimeValueArgumentType.timeValue()) + .executes(ctx -> { + set_time_current_world((Player) ctx.getSource().getSender(), time_value(ctx)); + return SINGLE_SUCCESS; + }) + .then( + argument("world", ArgumentTypes.world()).executes(ctx -> { + set_time(time_value(ctx), ctx.getArgument("world", World.class)); + return SINGLE_SUCCESS; + }) + ) + ); + } + + private TimeValue time_value(CommandContext ctx) { + return ctx.getArgument("time", TimeValue.class); + } + + private void set_time_current_world(Player player, TimeValue t) { + change_time_smoothly(player.getWorld(), get_module(), t.ticks(), 100); + } + + private void set_time(TimeValue t, World world) { + change_time_smoothly(world, get_module(), t.ticks(), 100); + } } diff --git a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Weather.java b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Weather.java index b68aaecdb..683bde357 100644 --- a/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Weather.java +++ b/vane-admin/src/main/java/org/oddlama/vane/admin/commands/Weather.java @@ -3,6 +3,10 @@ import static com.mojang.brigadier.Command.SINGLE_SUCCESS; import static io.papermc.paper.command.brigadier.Commands.argument; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -13,41 +17,46 @@ import org.oddlama.vane.core.command.enums.WeatherValue; import org.oddlama.vane.core.module.Context; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; - -import io.papermc.paper.command.brigadier.CommandSourceStack; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; - @Name("weather") public class Weather extends Command { - public Weather(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .then(help()) - .then(argument("weather", WeatherArgumentType.weather()) - .executes(ctx -> { set_weather_current_world((Player) ctx.getSource().getSender(), weather(ctx)); return SINGLE_SUCCESS;}) - .then(argument("world", ArgumentTypes.world()) - .executes(ctx -> { set_weather(ctx.getSource().getSender(), weather(ctx), ctx.getArgument("world", World.class)); return SINGLE_SUCCESS; }) - ) - ); - } - - private WeatherValue weather(CommandContext ctx){ - return ctx.getArgument("weather", WeatherValue.class); - } - - private void set_weather_current_world(Player player, WeatherValue w) { - set_weather(player, w, player.getWorld()); - } - - private void set_weather(CommandSender sender, WeatherValue w, World world) { - world.setStorm(w.storm()); - world.setThundering(w.thunder()); - } + public Weather(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .then(help()) + .then( + argument("weather", WeatherArgumentType.weather()) + .executes(ctx -> { + set_weather_current_world((Player) ctx.getSource().getSender(), weather(ctx)); + return SINGLE_SUCCESS; + }) + .then( + argument("world", ArgumentTypes.world()).executes(ctx -> { + set_weather( + ctx.getSource().getSender(), + weather(ctx), + ctx.getArgument("world", World.class) + ); + return SINGLE_SUCCESS; + }) + ) + ); + } + + private WeatherValue weather(CommandContext ctx) { + return ctx.getArgument("weather", WeatherValue.class); + } + + private void set_weather_current_world(Player player, WeatherValue w) { + set_weather(player, w, player.getWorld()); + } + + private void set_weather(CommandSender sender, WeatherValue w, World world) { + world.setStorm(w.storm()); + world.setThundering(w.thunder()); + } } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/VaneModule.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/VaneModule.java index b251ebeba..4afb4410f 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/VaneModule.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/VaneModule.java @@ -8,13 +8,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface VaneModule { - String name(); + String name(); - int bstats() default -1; + int bstats() default -1; - long config_version(); + long config_version(); - long lang_version(); + long lang_version(); - long storage_version(); + long storage_version(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Aliases.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Aliases.java index 587673094..dd66b984e 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Aliases.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Aliases.java @@ -8,5 +8,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Aliases { - String[] value(); + String[] value(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Name.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Name.java index 5212e9fab..cb0b95f81 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Name.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/command/Name.java @@ -8,5 +8,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Name { - String value(); + String value(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigBoolean.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigBoolean.java index 62f367fcc..facb9e1da 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigBoolean.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigBoolean.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigBoolean { - boolean def(); + boolean def(); - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDict.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDict.java index 71f9fdcf6..dceb11228 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDict.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDict.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigDict { - Class cls(); + Class cls(); - String desc(); + String desc(); - boolean metrics() default false; + boolean metrics() default false; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDouble.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDouble.java index ca0f716d5..dae4398a5 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDouble.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDouble.java @@ -8,13 +8,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigDouble { - double def(); + double def(); - double min() default Double.NaN; + double min() default Double.NaN; - double max() default Double.NaN; + double max() default Double.NaN; - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDoubleList.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDoubleList.java index b068fc570..6920a3986 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDoubleList.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigDoubleList.java @@ -8,13 +8,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigDoubleList { - double[] def(); + double[] def(); - double min() default Double.NaN; + double min() default Double.NaN; - double max() default Double.NaN; + double max() default Double.NaN; - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigExtendedMaterial.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigExtendedMaterial.java index e1ad637eb..712a13a3b 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigExtendedMaterial.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigExtendedMaterial.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigExtendedMaterial { - String def(); + String def(); - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigInt.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigInt.java index d8f25a78e..d2ff79e40 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigInt.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigInt.java @@ -8,13 +8,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigInt { - int def(); + int def(); - int min() default Integer.MIN_VALUE; + int min() default Integer.MIN_VALUE; - int max() default Integer.MAX_VALUE; + int max() default Integer.MAX_VALUE; - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigIntList.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigIntList.java index 2cdc03163..9bb5d9120 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigIntList.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigIntList.java @@ -8,13 +8,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigIntList { - int[] def(); + int[] def(); - int min() default Integer.MIN_VALUE; + int min() default Integer.MIN_VALUE; - int max() default Integer.MAX_VALUE; + int max() default Integer.MAX_VALUE; - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStack.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStack.java index ef3365ca5..db465e00f 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStack.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStack.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigItemStack { - ConfigItemStackDef def(); + ConfigItemStackDef def(); - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStackDef.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStackDef.java index 1c30bc258..f9abfe0a0 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStackDef.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigItemStackDef.java @@ -9,7 +9,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigItemStackDef { - Material type(); + Material type(); - int amount() default 1; + int amount() default 1; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigLong.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigLong.java index ef30a7e96..85ec6ab2e 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigLong.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigLong.java @@ -8,13 +8,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigLong { - long def(); + long def(); - long min() default Long.MIN_VALUE; + long min() default Long.MIN_VALUE; - long max() default Long.MAX_VALUE; + long max() default Long.MAX_VALUE; - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterial.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterial.java index 3def55f1c..d64525b15 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterial.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterial.java @@ -9,9 +9,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigMaterial { - Material def(); + Material def(); - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapEntry.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapEntry.java index ff4916ee8..007d9c58c 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapEntry.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapEntry.java @@ -9,7 +9,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigMaterialMapEntry { - String key(); + String key(); - Material value(); + Material value(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapEntry.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapEntry.java index 1c709c83c..062e3798c 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapEntry.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapEntry.java @@ -8,7 +8,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigMaterialMapMapEntry { - String key(); + String key(); - ConfigMaterialMapEntry[] value(); + ConfigMaterialMapEntry[] value(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMap.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMap.java index 600380c10..13fbc662c 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMap.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMap.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigMaterialMapMapMap { - ConfigMaterialMapMapMapEntry[] def(); + ConfigMaterialMapMapMapEntry[] def(); - String desc(); + String desc(); - boolean metrics() default false; + boolean metrics() default false; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMapEntry.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMapEntry.java index f8c33cbe8..c5cb9727b 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMapEntry.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialMapMapMapEntry.java @@ -8,7 +8,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigMaterialMapMapMapEntry { - String key(); + String key(); - ConfigMaterialMapMapEntry[] value(); + ConfigMaterialMapMapEntry[] value(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialSet.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialSet.java index ddb22a171..2d20c0d10 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialSet.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigMaterialSet.java @@ -9,9 +9,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigMaterialSet { - Material[] def(); + Material[] def(); - String desc(); + String desc(); - boolean metrics() default true; + boolean metrics() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigString.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigString.java index a26fe89d0..abfc979fd 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigString.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigString.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigString { - String def(); + String def(); - String desc(); + String desc(); - boolean metrics() default false; + boolean metrics() default false; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringList.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringList.java index 50415ff47..fbf7cf6bf 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringList.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringList.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigStringList { - String[] def(); + String[] def(); - String desc(); + String desc(); - boolean metrics() default false; + boolean metrics() default false; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMap.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMap.java index aaf66de7d..8ec410fb2 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMap.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMap.java @@ -8,9 +8,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigStringListMap { - ConfigStringListMapEntry[] def(); + ConfigStringListMapEntry[] def(); - String desc(); + String desc(); - boolean metrics() default false; + boolean metrics() default false; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMapEntry.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMapEntry.java index f5628a191..93598cfc8 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMapEntry.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/config/ConfigStringListMapEntry.java @@ -8,7 +8,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConfigStringListMapEntry { - String key(); + String key(); - String[] list(); + String[] list(); } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/Rarity.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/Rarity.java index c5113f80c..b4cad92f2 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/Rarity.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/Rarity.java @@ -1,8 +1,8 @@ package org.oddlama.vane.annotation.enchantment; public enum Rarity { - COMMON, - UNCOMMON, - RARE, - VERY_RARE, + COMMON, + UNCOMMON, + RARE, + VERY_RARE, } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/VaneEnchantment.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/VaneEnchantment.java index 9bf38ec83..53848fa87 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/VaneEnchantment.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/enchantment/VaneEnchantment.java @@ -9,21 +9,21 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface VaneEnchantment { - String name(); + String name(); - int max_level() default 1; + int max_level() default 1; - Rarity rarity() default Rarity.COMMON; + Rarity rarity() default Rarity.COMMON; - boolean curse() default false; + boolean curse() default false; - boolean tradeable() default false; + boolean tradeable() default false; - boolean treasure() default false; + boolean treasure() default false; - boolean generate_in_treasure() default false; + boolean generate_in_treasure() default false; - EnchantmentTarget target() default EnchantmentTarget.BREAKABLE; + EnchantmentTarget target() default EnchantmentTarget.BREAKABLE; - boolean allow_custom() default false; + boolean allow_custom() default false; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/item/VaneItem.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/item/VaneItem.java index d045d44ce..6f7cd9eff 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/item/VaneItem.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/item/VaneItem.java @@ -4,16 +4,20 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.bukkit.Material; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface VaneItem { - String name(); - Material base(); - int model_data(); - int version(); - int durability() default 0; - boolean enabled() default true; + String name(); + + Material base(); + + int model_data(); + + int version(); + + int durability() default 0; + + boolean enabled() default true; } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/CommandAnnotationProcessor.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/CommandAnnotationProcessor.java index b9d45d63c..d6dafbbb7 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/CommandAnnotationProcessor.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/CommandAnnotationProcessor.java @@ -14,78 +14,78 @@ import org.oddlama.vane.annotation.command.Name; @SupportedAnnotationTypes( - { - "org.oddlama.vane.annotation.command.Aliases", - "org.oddlama.vane.annotation.command.Name", - "org.oddlama.vane.annotation.command.VaneCommand", - } + { + "org.oddlama.vane.annotation.command.Aliases", + "org.oddlama.vane.annotation.command.Name", + "org.oddlama.vane.annotation.command.VaneCommand", + } ) @SupportedSourceVersion(SourceVersion.RELEASE_21) public class CommandAnnotationProcessor extends AbstractProcessor { - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static final Class[] mandatory_annotations = new Class[] { Name.class }; + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static final Class[] mandatory_annotations = new Class[] { Name.class }; - @Override - public boolean process(Set annotations, RoundEnvironment round_env) { - for (var annotation : annotations) { - round_env.getElementsAnnotatedWith(annotation).forEach(e -> verify_is_class(annotation, e)); - round_env.getElementsAnnotatedWith(annotation).forEach(e -> verify_extends_command(annotation, e)); + @Override + public boolean process(Set annotations, RoundEnvironment round_env) { + for (var annotation : annotations) { + round_env.getElementsAnnotatedWith(annotation).forEach(e -> verify_is_class(annotation, e)); + round_env.getElementsAnnotatedWith(annotation).forEach(e -> verify_extends_command(annotation, e)); - // Verify that all mandatory annotations are present - if (annotation.asType().toString().equals("org.oddlama.vane.annotation.command.VaneCommand")) { - round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_has_annotations); - } - } + // Verify that all mandatory annotations are present + if (annotation.asType().toString().equals("org.oddlama.vane.annotation.command.VaneCommand")) { + round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_has_annotations); + } + } - return true; - } + return true; + } - private void verify_has_annotations(Element element) { - // Only check subclasses - if (element.asType().toString().startsWith("org.oddlama.vane.core.command.Command<")) { - return; - } + private void verify_has_annotations(Element element) { + // Only check subclasses + if (element.asType().toString().startsWith("org.oddlama.vane.core.command.Command<")) { + return; + } - for (var a_cls : mandatory_annotations) { - if (element.getAnnotation(a_cls) == null) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + ": missing @" + a_cls.getSimpleName() + " annotation" - ); - } - } - } + for (var a_cls : mandatory_annotations) { + if (element.getAnnotation(a_cls) == null) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + ": missing @" + a_cls.getSimpleName() + " annotation" + ); + } + } + } - private void verify_is_class(TypeElement annotation, Element element) { - if (element.getKind() != ElementKind.CLASS) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + ": @" + annotation.getSimpleName() + " must be applied to a class" - ); - } - } + private void verify_is_class(TypeElement annotation, Element element) { + if (element.getKind() != ElementKind.CLASS) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + ": @" + annotation.getSimpleName() + " must be applied to a class" + ); + } + } - private void verify_extends_command(TypeElement annotation, Element element) { - var t = (TypeElement) element; - if ( - !t.toString().equals("org.oddlama.vane.core.command.Command") && - !t.getSuperclass().toString().startsWith("org.oddlama.vane.core.command.Command<") - ) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + - ": @" + - annotation.getSimpleName() + - " must be applied to a class inheriting from org.oddlama.vane.core.command.Command, but it inherits from " + - t.getSuperclass().toString() - ); - } - } + private void verify_extends_command(TypeElement annotation, Element element) { + var t = (TypeElement) element; + if ( + !t.toString().equals("org.oddlama.vane.core.command.Command") && + !t.getSuperclass().toString().startsWith("org.oddlama.vane.core.command.Command<") + ) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + + ": @" + + annotation.getSimpleName() + + " must be applied to a class inheriting from org.oddlama.vane.core.command.Command, but it inherits from " + + t.getSuperclass().toString() + ); + } + } } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/ConfigAndLangProcessor.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/ConfigAndLangProcessor.java index fae2373b7..d63a3ecef 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/ConfigAndLangProcessor.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/ConfigAndLangProcessor.java @@ -14,106 +14,106 @@ import javax.tools.Diagnostic; @SupportedAnnotationTypes( - { - "org.oddlama.vane.annotation.config.ConfigBoolean", - "org.oddlama.vane.annotation.config.ConfigDict", - "org.oddlama.vane.annotation.config.ConfigDouble", - "org.oddlama.vane.annotation.config.ConfigDoubleList", - "org.oddlama.vane.annotation.config.ConfigExtendedMaterial", - "org.oddlama.vane.annotation.config.ConfigInt", - "org.oddlama.vane.annotation.config.ConfigIntList", - "org.oddlama.vane.annotation.config.ConfigItemStack", - "org.oddlama.vane.annotation.config.ConfigLong", - "org.oddlama.vane.annotation.config.ConfigMaterial", - "org.oddlama.vane.annotation.config.ConfigMaterialMapMapMap", - "org.oddlama.vane.annotation.config.ConfigMaterialSet", - "org.oddlama.vane.annotation.config.ConfigString", - "org.oddlama.vane.annotation.config.ConfigStringList", - "org.oddlama.vane.annotation.config.ConfigStringListMap", - "org.oddlama.vane.annotation.config.ConfigVersion", - "org.oddlama.vane.annotation.lang.LangMessage", - "org.oddlama.vane.annotation.lang.LangMessageArray", - "org.oddlama.vane.annotation.lang.LangVersion", - } + { + "org.oddlama.vane.annotation.config.ConfigBoolean", + "org.oddlama.vane.annotation.config.ConfigDict", + "org.oddlama.vane.annotation.config.ConfigDouble", + "org.oddlama.vane.annotation.config.ConfigDoubleList", + "org.oddlama.vane.annotation.config.ConfigExtendedMaterial", + "org.oddlama.vane.annotation.config.ConfigInt", + "org.oddlama.vane.annotation.config.ConfigIntList", + "org.oddlama.vane.annotation.config.ConfigItemStack", + "org.oddlama.vane.annotation.config.ConfigLong", + "org.oddlama.vane.annotation.config.ConfigMaterial", + "org.oddlama.vane.annotation.config.ConfigMaterialMapMapMap", + "org.oddlama.vane.annotation.config.ConfigMaterialSet", + "org.oddlama.vane.annotation.config.ConfigString", + "org.oddlama.vane.annotation.config.ConfigStringList", + "org.oddlama.vane.annotation.config.ConfigStringListMap", + "org.oddlama.vane.annotation.config.ConfigVersion", + "org.oddlama.vane.annotation.lang.LangMessage", + "org.oddlama.vane.annotation.lang.LangMessageArray", + "org.oddlama.vane.annotation.lang.LangVersion", + } ) @SupportedSourceVersion(SourceVersion.RELEASE_21) public class ConfigAndLangProcessor extends AbstractProcessor { - @Override - public boolean process(Set annotations, RoundEnvironment round_env) { - for (var annotation : annotations) { - round_env.getElementsAnnotatedWith(annotation).forEach(e -> verify_type(annotation, e)); - } + @Override + public boolean process(Set annotations, RoundEnvironment round_env) { + for (var annotation : annotations) { + round_env.getElementsAnnotatedWith(annotation).forEach(e -> verify_type(annotation, e)); + } - return true; - } + return true; + } - private static final Map field_type_mapping; + private static final Map field_type_mapping; - static { - Map map = new HashMap<>(); - map.put("org.oddlama.vane.annotation.config.ConfigBoolean", "boolean"); - map.put("org.oddlama.vane.annotation.config.ConfigDict", ""); - map.put("org.oddlama.vane.annotation.config.ConfigDouble", "double"); - map.put("org.oddlama.vane.annotation.config.ConfigDoubleList", "java.util.List"); - map.put( - "org.oddlama.vane.annotation.config.ConfigExtendedMaterial", - "org.oddlama.vane.core.material.ExtendedMaterial" - ); - map.put("org.oddlama.vane.annotation.config.ConfigInt", "int"); - map.put("org.oddlama.vane.annotation.config.ConfigIntList", "java.util.List"); - map.put("org.oddlama.vane.annotation.config.ConfigItemStack", "org.bukkit.inventory.ItemStack"); - map.put("org.oddlama.vane.annotation.config.ConfigLong", "long"); - map.put("org.oddlama.vane.annotation.config.ConfigMaterial", "org.bukkit.Material"); - map.put( - "org.oddlama.vane.annotation.config.ConfigMaterialMapMapMap", - "java.util.Map>>" - ); - map.put("org.oddlama.vane.annotation.config.ConfigMaterialSet", "java.util.Set"); - map.put("org.oddlama.vane.annotation.config.ConfigString", "java.lang.String"); - map.put("org.oddlama.vane.annotation.config.ConfigStringList", "java.util.List"); - map.put( - "org.oddlama.vane.annotation.config.ConfigStringListMap", - "java.util.Map>" - ); - map.put("org.oddlama.vane.annotation.config.ConfigVersion", "long"); - map.put("org.oddlama.vane.annotation.lang.LangMessage", "org.oddlama.vane.core.lang.TranslatedMessage"); - map.put( - "org.oddlama.vane.annotation.lang.LangMessageArray", - "org.oddlama.vane.core.lang.TranslatedMessageArray" - ); - map.put("org.oddlama.vane.annotation.lang.LangVersion", "long"); - field_type_mapping = Collections.unmodifiableMap(map); - } + static { + Map map = new HashMap<>(); + map.put("org.oddlama.vane.annotation.config.ConfigBoolean", "boolean"); + map.put("org.oddlama.vane.annotation.config.ConfigDict", ""); + map.put("org.oddlama.vane.annotation.config.ConfigDouble", "double"); + map.put("org.oddlama.vane.annotation.config.ConfigDoubleList", "java.util.List"); + map.put( + "org.oddlama.vane.annotation.config.ConfigExtendedMaterial", + "org.oddlama.vane.core.material.ExtendedMaterial" + ); + map.put("org.oddlama.vane.annotation.config.ConfigInt", "int"); + map.put("org.oddlama.vane.annotation.config.ConfigIntList", "java.util.List"); + map.put("org.oddlama.vane.annotation.config.ConfigItemStack", "org.bukkit.inventory.ItemStack"); + map.put("org.oddlama.vane.annotation.config.ConfigLong", "long"); + map.put("org.oddlama.vane.annotation.config.ConfigMaterial", "org.bukkit.Material"); + map.put( + "org.oddlama.vane.annotation.config.ConfigMaterialMapMapMap", + "java.util.Map>>" + ); + map.put("org.oddlama.vane.annotation.config.ConfigMaterialSet", "java.util.Set"); + map.put("org.oddlama.vane.annotation.config.ConfigString", "java.lang.String"); + map.put("org.oddlama.vane.annotation.config.ConfigStringList", "java.util.List"); + map.put( + "org.oddlama.vane.annotation.config.ConfigStringListMap", + "java.util.Map>" + ); + map.put("org.oddlama.vane.annotation.config.ConfigVersion", "long"); + map.put("org.oddlama.vane.annotation.lang.LangMessage", "org.oddlama.vane.core.lang.TranslatedMessage"); + map.put( + "org.oddlama.vane.annotation.lang.LangMessageArray", + "org.oddlama.vane.core.lang.TranslatedMessageArray" + ); + map.put("org.oddlama.vane.annotation.lang.LangVersion", "long"); + field_type_mapping = Collections.unmodifiableMap(map); + } - private void verify_type(TypeElement annotation, Element element) { - var type = element.asType().toString(); - var required_type = field_type_mapping.get(annotation.asType().toString()); - if (required_type == null) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + - ": @" + - annotation.getSimpleName() + - " has no required_type mapping! This is a bug." - ); - } else { - if (!required_type.equals("") && !required_type.equals(type)) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + - ": @" + - annotation.getSimpleName() + - " requires a field of type " + - required_type + - " but got " + - type - ); - } - } - } + private void verify_type(TypeElement annotation, Element element) { + var type = element.asType().toString(); + var required_type = field_type_mapping.get(annotation.asType().toString()); + if (required_type == null) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + + ": @" + + annotation.getSimpleName() + + " has no required_type mapping! This is a bug." + ); + } else { + if (!required_type.equals("") && !required_type.equals(type)) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + + ": @" + + annotation.getSimpleName() + + " requires a field of type " + + required_type + + " but got " + + type + ); + } + } + } } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneEnchantmentProcessor.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneEnchantmentProcessor.java index 38242a0b0..6047ce897 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneEnchantmentProcessor.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneEnchantmentProcessor.java @@ -16,46 +16,46 @@ @SupportedSourceVersion(SourceVersion.RELEASE_21) public class VaneEnchantmentProcessor extends AbstractProcessor { - @Override - public boolean process(Set annotations, RoundEnvironment round_env) { - for (var annotation : annotations) { - round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_is_class); - round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_extends_module); - } + @Override + public boolean process(Set annotations, RoundEnvironment round_env) { + for (var annotation : annotations) { + round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_is_class); + round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_extends_module); + } - return true; - } + return true; + } - private void verify_is_class(Element element) { - if (element.getKind() != ElementKind.CLASS) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + ": @VaneEnchantment must be applied to a class" - ); - } - } + private void verify_is_class(Element element) { + if (element.getKind() != ElementKind.CLASS) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + ": @VaneEnchantment must be applied to a class" + ); + } + } - private void verify_extends_module(Element element) { - var t = (TypeElement) element; - while (true) { - if (t.asType().toString().startsWith("org.oddlama.vane.core.enchantments.CustomEnchantment<")) { - return; - } - if (t.getSuperclass() instanceof DeclaredType) { - t = (TypeElement) ((DeclaredType) t.getSuperclass()).asElement(); - } else { - break; - } - } + private void verify_extends_module(Element element) { + var t = (TypeElement) element; + while (true) { + if (t.asType().toString().startsWith("org.oddlama.vane.core.enchantments.CustomEnchantment<")) { + return; + } + if (t.getSuperclass() instanceof DeclaredType) { + t = (TypeElement) ((DeclaredType) t.getSuperclass()).asElement(); + } else { + break; + } + } - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + - ": @VaneEnchantment must be applied to a class inheriting from org.oddlama.vane.core.enchantments.CustomEnchantment" - ); - } + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + + ": @VaneEnchantment must be applied to a class inheriting from org.oddlama.vane.core.enchantments.CustomEnchantment" + ); + } } diff --git a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneModuleProcessor.java b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneModuleProcessor.java index cbcb63baf..a47fbcf45 100644 --- a/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneModuleProcessor.java +++ b/vane-annotations/src/main/java/org/oddlama/vane/annotation/processor/VaneModuleProcessor.java @@ -16,46 +16,46 @@ @SupportedSourceVersion(SourceVersion.RELEASE_21) public class VaneModuleProcessor extends AbstractProcessor { - @Override - public boolean process(Set annotations, RoundEnvironment round_env) { - for (var annotation : annotations) { - round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_is_class); - round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_extends_module); - } + @Override + public boolean process(Set annotations, RoundEnvironment round_env) { + for (var annotation : annotations) { + round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_is_class); + round_env.getElementsAnnotatedWith(annotation).forEach(this::verify_extends_module); + } - return true; - } + return true; + } - private void verify_is_class(Element element) { - if (element.getKind() != ElementKind.CLASS) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + ": @VaneModule must be applied to a class" - ); - } - } + private void verify_is_class(Element element) { + if (element.getKind() != ElementKind.CLASS) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + ": @VaneModule must be applied to a class" + ); + } + } - private void verify_extends_module(Element element) { - var t = (TypeElement) element; - while (true) { - if (t.asType().toString().startsWith("org.oddlama.vane.core.module.Module<")) { - return; - } - if (t.getSuperclass() instanceof DeclaredType) { - t = (TypeElement) ((DeclaredType) t.getSuperclass()).asElement(); - } else { - break; - } - } + private void verify_extends_module(Element element) { + var t = (TypeElement) element; + while (true) { + if (t.asType().toString().startsWith("org.oddlama.vane.core.module.Module<")) { + return; + } + if (t.getSuperclass() instanceof DeclaredType) { + t = (TypeElement) ((DeclaredType) t.getSuperclass()).asElement(); + } else { + break; + } + } - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, - element.asType().toString() + - ": @VaneModule must be applied to a class inheriting from org.oddlama.vane.core.Module" - ); - } + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + element.asType().toString() + + ": @VaneModule must be applied to a class inheriting from org.oddlama.vane.core.Module" + ); + } } diff --git a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/Bedtime.java b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/Bedtime.java index dde85385e..02dfa21d9 100644 --- a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/Bedtime.java +++ b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/Bedtime.java @@ -24,197 +24,202 @@ @VaneModule(name = "bedtime", bstats = 8639, config_version = 3, lang_version = 5, storage_version = 1) public class Bedtime extends Module { - // One set of sleeping players per world, to keep track - private HashMap> world_sleepers = new HashMap<>(); - - // Configuration - @ConfigDouble( - def = 0.5, - min = 0.0, - max = 1.0, - desc = "The percentage of sleeping players required to advance time." - ) - double config_sleep_threshold; - - @ConfigLong( - def = 1000, - min = 0, - max = 12000, - desc = "The target time in ticks to advance to. 1000 is just after sunrise." - ) - long config_target_time; - - @ConfigLong(def = 100, min = 0, max = 1200, desc = "The interpolation time in ticks for a smooth change of time.") - long config_interpolation_ticks; - - // Language - @LangMessage - private TranslatedMessage lang_player_bed_enter; - - @LangMessage - private TranslatedMessage lang_player_bed_leave; - - public BedtimeDynmapLayer dynmap_layer; - public BedtimeBlueMapLayer blue_map_layer; - - public Bedtime() { - dynmap_layer = new BedtimeDynmapLayer(this); - blue_map_layer = new BedtimeBlueMapLayer(this); - } - - public void start_check_world_task(final World world) { - if (enough_players_sleeping(world)) { - schedule_task( - () -> { - check_world_now(world); - // Subtract two ticks so this runs one tick before minecraft would - // advance time (if all players are asleep), which would effectively cancel the task. - }, - 100 - 2 - ); - } - } - - public void check_world_now(final World world) { - // Abort task if condition changed - if (!enough_players_sleeping(world)) { - return; - } - - // Let the sun rise, and set weather - change_time_smoothly(world, this, config_target_time, config_interpolation_ticks); - world.setStorm(false); - world.setThundering(false); - - // Clear sleepers - reset_sleepers(world); - - // Wakeup players as if they were actually sleeping through the night - world - .getPlayers() - .stream() - .filter(Player::isSleeping) - .forEach(p -> { - // skipSleepTimer = false (-> set sleepCounter to 100) - // updateSleepingPlayers = false - Nms.get_player(p).stopSleepInBed(false, false); - }); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_bed_enter(PlayerBedEnterEvent event) { - final var player = event.getPlayer(); - final var world = player.getWorld(); - - // Update marker - dynmap_layer.update_marker(player); - blue_map_layer.update_marker(player); - - schedule_next_tick(() -> { - // Register the new player as sleeping - add_sleeping(world, player); - // Start a sleep check task - start_check_world_task(world); - }); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_bed_leave(PlayerBedLeaveEvent event) { - remove_sleeping(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_quit(PlayerQuitEvent event) { - // Start a sleep check task - start_check_world_task(event.getPlayer().getWorld()); - } - - private static String percentage_str(double percentage) { - return String.format("§6%.2f", 100.0 * percentage) + "%"; - } - - private long get_amount_sleeping(final World world) { - //return world.getPlayers().stream() - // .filter(p -> p.getGameMode() != GameMode.SPECTATOR) - // .filter(p -> p.isSleeping()) - // .count(); - - final var world_id = world.getUID(); - var sleepers = world_sleepers.get(world_id); - if (sleepers == null) { - return 0; - } - return sleepers.size(); - } - - private long get_potential_sleepers_in_world(final World world) { - return world.getPlayers().stream().filter(p -> p.getGameMode() != GameMode.SPECTATOR).count(); - } - - private double get_percentage_sleeping(final World world) { - final var count_sleeping = get_amount_sleeping(world); - if (count_sleeping == 0) { - return 0.0; - } - - return (double)count_sleeping / get_potential_sleepers_in_world(world); - } - - private boolean enough_players_sleeping(final World world) { - return get_percentage_sleeping(world) >= config_sleep_threshold; - } - - private void add_sleeping(final World world, final Player player) { - // Add player to sleepers - final var world_id = world.getUID(); - var sleepers = world_sleepers.computeIfAbsent(world_id, k -> new HashSet<>()); - - sleepers.add(player.getUniqueId()); - - // Broadcast a sleeping message - var percent = get_percentage_sleeping(world); - var count_sleeping = get_amount_sleeping(world); - var count_required = (int)Math.ceil(get_potential_sleepers_in_world(world) * config_sleep_threshold); - lang_player_bed_enter.broadcast_world_action_bar(world, - "§6" + player.getName(), - "§6" + percentage_str(percent), - String.valueOf(count_sleeping), - String.valueOf(count_required), - "§6" + world.getName()); - } - - private void remove_sleeping(Player player) { - final var world = player.getWorld(); - final var world_id = world.getUID(); - - // Remove player from sleepers - final var sleepers = world_sleepers.get(world_id); - if (sleepers == null) { - // No sleepers in this world. Abort. - return; - } - - if (sleepers.remove(player.getUniqueId())) { - // Broadcast a sleeping message - var percent = get_percentage_sleeping(world); - var count_sleeping = get_amount_sleeping(world); - var count_required = (int)Math.ceil(get_potential_sleepers_in_world(world) * config_sleep_threshold); - lang_player_bed_leave.broadcast_world_action_bar(world, - "§6" + player.getName(), - "§6" + percentage_str(percent), - String.valueOf(count_sleeping), - String.valueOf(count_required), - "§6" + world.getName()); - } - } - - private void reset_sleepers(World world) { - final var world_id = world.getUID(); - final var sleepers = world_sleepers.get(world_id); - if (sleepers == null) { - return; - } - - sleepers.clear(); - } + // One set of sleeping players per world, to keep track + private HashMap> world_sleepers = new HashMap<>(); + + // Configuration + @ConfigDouble( + def = 0.5, + min = 0.0, + max = 1.0, + desc = "The percentage of sleeping players required to advance time." + ) + double config_sleep_threshold; + + @ConfigLong( + def = 1000, + min = 0, + max = 12000, + desc = "The target time in ticks to advance to. 1000 is just after sunrise." + ) + long config_target_time; + + @ConfigLong(def = 100, min = 0, max = 1200, desc = "The interpolation time in ticks for a smooth change of time.") + long config_interpolation_ticks; + + // Language + @LangMessage + private TranslatedMessage lang_player_bed_enter; + + @LangMessage + private TranslatedMessage lang_player_bed_leave; + + public BedtimeDynmapLayer dynmap_layer; + public BedtimeBlueMapLayer blue_map_layer; + + public Bedtime() { + dynmap_layer = new BedtimeDynmapLayer(this); + blue_map_layer = new BedtimeBlueMapLayer(this); + } + + public void start_check_world_task(final World world) { + if (enough_players_sleeping(world)) { + schedule_task( + () -> { + check_world_now(world); + // Subtract two ticks so this runs one tick before minecraft would + // advance time (if all players are asleep), which would effectively cancel + // the task. + }, + 100 - 2 + ); + } + } + + public void check_world_now(final World world) { + // Abort task if condition changed + if (!enough_players_sleeping(world)) { + return; + } + + // Let the sun rise, and set weather + change_time_smoothly(world, this, config_target_time, config_interpolation_ticks); + world.setStorm(false); + world.setThundering(false); + + // Clear sleepers + reset_sleepers(world); + + // Wakeup players as if they were actually sleeping through the night + world + .getPlayers() + .stream() + .filter(Player::isSleeping) + .forEach(p -> { + // skipSleepTimer = false (-> set sleepCounter to 100) + // updateSleepingPlayers = false + Nms.get_player(p).stopSleepInBed(false, false); + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_bed_enter(PlayerBedEnterEvent event) { + final var player = event.getPlayer(); + final var world = player.getWorld(); + + // Update marker + dynmap_layer.update_marker(player); + blue_map_layer.update_marker(player); + + schedule_next_tick(() -> { + // Register the new player as sleeping + add_sleeping(world, player); + // Start a sleep check task + start_check_world_task(world); + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_bed_leave(PlayerBedLeaveEvent event) { + remove_sleeping(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_quit(PlayerQuitEvent event) { + // Start a sleep check task + start_check_world_task(event.getPlayer().getWorld()); + } + + private static String percentage_str(double percentage) { + return String.format("§6%.2f", 100.0 * percentage) + "%"; + } + + private long get_amount_sleeping(final World world) { + // return world.getPlayers().stream() + // .filter(p -> p.getGameMode() != GameMode.SPECTATOR) + // .filter(p -> p.isSleeping()) + // .count(); + + final var world_id = world.getUID(); + var sleepers = world_sleepers.get(world_id); + if (sleepers == null) { + return 0; + } + return sleepers.size(); + } + + private long get_potential_sleepers_in_world(final World world) { + return world.getPlayers().stream().filter(p -> p.getGameMode() != GameMode.SPECTATOR).count(); + } + + private double get_percentage_sleeping(final World world) { + final var count_sleeping = get_amount_sleeping(world); + if (count_sleeping == 0) { + return 0.0; + } + + return (double) count_sleeping / get_potential_sleepers_in_world(world); + } + + private boolean enough_players_sleeping(final World world) { + return get_percentage_sleeping(world) >= config_sleep_threshold; + } + + private void add_sleeping(final World world, final Player player) { + // Add player to sleepers + final var world_id = world.getUID(); + var sleepers = world_sleepers.computeIfAbsent(world_id, k -> new HashSet<>()); + + sleepers.add(player.getUniqueId()); + + // Broadcast a sleeping message + var percent = get_percentage_sleeping(world); + var count_sleeping = get_amount_sleeping(world); + var count_required = (int) Math.ceil(get_potential_sleepers_in_world(world) * config_sleep_threshold); + lang_player_bed_enter.broadcast_world_action_bar( + world, + "§6" + player.getName(), + "§6" + percentage_str(percent), + String.valueOf(count_sleeping), + String.valueOf(count_required), + "§6" + world.getName() + ); + } + + private void remove_sleeping(Player player) { + final var world = player.getWorld(); + final var world_id = world.getUID(); + + // Remove player from sleepers + final var sleepers = world_sleepers.get(world_id); + if (sleepers == null) { + // No sleepers in this world. Abort. + return; + } + + if (sleepers.remove(player.getUniqueId())) { + // Broadcast a sleeping message + var percent = get_percentage_sleeping(world); + var count_sleeping = get_amount_sleeping(world); + var count_required = (int) Math.ceil(get_potential_sleepers_in_world(world) * config_sleep_threshold); + lang_player_bed_leave.broadcast_world_action_bar( + world, + "§6" + player.getName(), + "§6" + percentage_str(percent), + String.valueOf(count_sleeping), + String.valueOf(count_required), + "§6" + world.getName() + ); + } + } + + private void reset_sleepers(World world) { + final var world_id = world.getUID(); + final var sleepers = world_sleepers.get(world_id); + if (sleepers == null) { + return; + } + + sleepers.clear(); + } } diff --git a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayer.java b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayer.java index 964604286..70a9e2a2e 100644 --- a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayer.java +++ b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayer.java @@ -10,59 +10,59 @@ public class BedtimeBlueMapLayer extends ModuleComponent { - @ConfigBoolean(def = false, desc = "If the marker set should be hidden by default.") - public boolean config_hide_by_default; + @ConfigBoolean(def = false, desc = "If the marker set should be hidden by default.") + public boolean config_hide_by_default; - @LangMessage - public TranslatedMessage lang_layer_label; + @LangMessage + public TranslatedMessage lang_layer_label; - @LangMessage - public TranslatedMessage lang_marker_label; + @LangMessage + public TranslatedMessage lang_marker_label; - private BedtimeBlueMapLayerDelegate delegate = null; + private BedtimeBlueMapLayerDelegate delegate = null; - public BedtimeBlueMapLayer(final Context context) { - super(context.group("blue_map", "Enable BlueMap integration to show player spawnpoints (beds).")); - } + public BedtimeBlueMapLayer(final Context context) { + super(context.group("blue_map", "Enable BlueMap integration to show player spawnpoints (beds).")); + } - public void delayed_on_enable() { - final var plugin = get_module().getServer().getPluginManager().getPlugin("BlueMap"); - if (plugin == null) { - return; - } + public void delayed_on_enable() { + final var plugin = get_module().getServer().getPluginManager().getPlugin("BlueMap"); + if (plugin == null) { + return; + } - delegate = new BedtimeBlueMapLayerDelegate(this); - delegate.on_enable(plugin); - } + delegate = new BedtimeBlueMapLayerDelegate(this); + delegate.on_enable(plugin); + } - @Override - public void on_enable() { - schedule_next_tick(this::delayed_on_enable); - } + @Override + public void on_enable() { + schedule_next_tick(this::delayed_on_enable); + } - @Override - public void on_disable() { - if (delegate != null) { - delegate.on_disable(); - delegate = null; - } - } + @Override + public void on_disable() { + if (delegate != null) { + delegate.on_disable(); + delegate = null; + } + } - public void update_marker(final OfflinePlayer player) { - if (delegate != null) { - delegate.update_marker(player); - } - } + public void update_marker(final OfflinePlayer player) { + if (delegate != null) { + delegate.update_marker(player); + } + } - public void remove_marker(final UUID player_id) { - if (delegate != null) { - delegate.remove_marker(player_id); - } - } + public void remove_marker(final UUID player_id) { + if (delegate != null) { + delegate.remove_marker(player_id); + } + } - public void update_all_markers() { - if (delegate != null) { - delegate.update_all_markers(); - } - } + public void update_all_markers() { + if (delegate != null) { + delegate.update_all_markers(); + } + } } diff --git a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayerDelegate.java b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayerDelegate.java index 50f6b6f74..718139883 100644 --- a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayerDelegate.java +++ b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeBlueMapLayerDelegate.java @@ -5,7 +5,6 @@ import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.markers.HtmlMarker; import de.bluecolored.bluemap.api.markers.MarkerSet; - import java.util.HashMap; import java.util.UUID; import org.bukkit.OfflinePlayer; @@ -14,92 +13,95 @@ public class BedtimeBlueMapLayerDelegate { - public static final String MARKER_SET_ID = "vane_bedtime.bedtime"; - - private final BedtimeBlueMapLayer parent; - - private boolean bluemap_enabled = false; - - public BedtimeBlueMapLayerDelegate(final BedtimeBlueMapLayer parent) { - this.parent = parent; - } - - public Bedtime get_module() { - return parent.get_module(); - } - - public void on_enable(final Plugin plugin) { - BlueMapAPI.onEnable(api -> { - get_module().log.info("Enabling BlueMap integration"); - bluemap_enabled = true; - - // Create marker sets - for (final var world : get_module().getServer().getWorlds()) { - create_marker_set(api, world); - } - - update_all_markers(); - }); - } - - public void on_disable() { - if (!bluemap_enabled) { - return; - } - - get_module().log.info("Disabling BlueMap integration"); - bluemap_enabled = false; - } - - // world_id -> MarkerSet - private final HashMap marker_sets = new HashMap<>(); - private void create_marker_set(final BlueMapAPI api, final World world) { - if (marker_sets.containsKey(world.getUID())) { - return; - } - - final var marker_set = MarkerSet.builder() - .label(parent.lang_layer_label.str()) - .toggleable(true) - .defaultHidden(parent.config_hide_by_default) - .build(); - - api.getWorld(world).ifPresent(bm_world -> { - for (final var map : bm_world.getMaps()) { - map.getMarkerSets().put(MARKER_SET_ID, marker_set); - } - }); - - marker_sets.put(world.getUID(), marker_set); - } - - public void update_marker(final OfflinePlayer player) { - remove_marker(player.getUniqueId()); - final var loc = player.getRespawnLocation(); - if (loc == null) { - return; - } - - final var marker = HtmlMarker.builder() - .position(loc.getX(), loc.getY(), loc.getZ()) - .label("Bed for " + player.getName()) - .html(parent.lang_marker_label.str(escapeHtml(player.getName()))) - .build(); - - // Existing markers will be overwritten. - marker_sets.get(loc.getWorld().getUID()).getMarkers().put(player.getUniqueId().toString(), marker); - } - - public void remove_marker(final UUID player_id) { - for (final var marker_set : marker_sets.values()) { - marker_set.getMarkers().remove(player_id.toString()); - } - } - - public void update_all_markers() { - // Update all existing - for (final var player : get_module().get_offline_players_with_valid_name()) { - update_marker(player); - } - } + public static final String MARKER_SET_ID = "vane_bedtime.bedtime"; + + private final BedtimeBlueMapLayer parent; + + private boolean bluemap_enabled = false; + + public BedtimeBlueMapLayerDelegate(final BedtimeBlueMapLayer parent) { + this.parent = parent; + } + + public Bedtime get_module() { + return parent.get_module(); + } + + public void on_enable(final Plugin plugin) { + BlueMapAPI.onEnable(api -> { + get_module().log.info("Enabling BlueMap integration"); + bluemap_enabled = true; + + // Create marker sets + for (final var world : get_module().getServer().getWorlds()) { + create_marker_set(api, world); + } + + update_all_markers(); + }); + } + + public void on_disable() { + if (!bluemap_enabled) { + return; + } + + get_module().log.info("Disabling BlueMap integration"); + bluemap_enabled = false; + } + + // world_id -> MarkerSet + private final HashMap marker_sets = new HashMap<>(); + + private void create_marker_set(final BlueMapAPI api, final World world) { + if (marker_sets.containsKey(world.getUID())) { + return; + } + + final var marker_set = MarkerSet.builder() + .label(parent.lang_layer_label.str()) + .toggleable(true) + .defaultHidden(parent.config_hide_by_default) + .build(); + + api + .getWorld(world) + .ifPresent(bm_world -> { + for (final var map : bm_world.getMaps()) { + map.getMarkerSets().put(MARKER_SET_ID, marker_set); + } + }); + + marker_sets.put(world.getUID(), marker_set); + } + + public void update_marker(final OfflinePlayer player) { + remove_marker(player.getUniqueId()); + final var loc = player.getRespawnLocation(); + if (loc == null) { + return; + } + + final var marker = HtmlMarker.builder() + .position(loc.getX(), loc.getY(), loc.getZ()) + .label("Bed for " + player.getName()) + .html(parent.lang_marker_label.str(escapeHtml(player.getName()))) + .build(); + + // Existing markers will be overwritten. + marker_sets.get(loc.getWorld().getUID()).getMarkers().put(player.getUniqueId().toString(), marker); + } + + public void remove_marker(final UUID player_id) { + for (final var marker_set : marker_sets.values()) { + marker_set.getMarkers().remove(player_id.toString()); + } + } + + public void update_all_markers() { + // Update all existing + for (final var player : get_module().get_offline_players_with_valid_name()) { + update_marker(player); + } + } } diff --git a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayer.java b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayer.java index 76c91d862..79629cfcd 100644 --- a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayer.java +++ b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayer.java @@ -12,78 +12,78 @@ public class BedtimeDynmapLayer extends ModuleComponent { - public static final String LAYER_ID = "vane_bedtime.bedtime"; - - @ConfigInt(def = 25, min = 0, desc = "Layer ordering priority.") - public int config_layer_priority; - - @ConfigBoolean(def = false, desc = "If the layer should be hidden by default.") - public boolean config_layer_hide; - - @ConfigString(def = "house", desc = "The dynmap marker icon.") - public String config_marker_icon; - - @LangMessage - public TranslatedMessage lang_layer_label; - - @LangMessage - public TranslatedMessage lang_marker_label; - - private BedtimeDynmapLayerDelegate delegate = null; - - public BedtimeDynmapLayer(final Context context) { - super( - context.group( - "dynmap", - "Enable dynmap integration. Player spawnpoints (beds) will then be shown on a separate dynmap layer." - ) - ); - } - - public void delayed_on_enable() { - final var plugin = get_module().getServer().getPluginManager().getPlugin("dynmap"); - if (plugin == null) { - return; - } - - delegate = new BedtimeDynmapLayerDelegate(this); - delegate.on_enable(plugin); - } - - @Override - public void on_enable() { - schedule_next_tick(this::delayed_on_enable); - } - - @Override - public void on_disable() { - if (delegate != null) { - delegate.on_disable(); - delegate = null; - } - } - - public void update_marker(final OfflinePlayer player) { - if (delegate != null) { - delegate.update_marker(player); - } - } - - public void remove_marker(final UUID player_id) { - if (delegate != null) { - delegate.remove_marker(player_id); - } - } - - public void remove_marker(final String marker_id) { - if (delegate != null) { - delegate.remove_marker(marker_id); - } - } - - public void update_all_markers() { - if (delegate != null) { - delegate.update_all_markers(); - } - } + public static final String LAYER_ID = "vane_bedtime.bedtime"; + + @ConfigInt(def = 25, min = 0, desc = "Layer ordering priority.") + public int config_layer_priority; + + @ConfigBoolean(def = false, desc = "If the layer should be hidden by default.") + public boolean config_layer_hide; + + @ConfigString(def = "house", desc = "The dynmap marker icon.") + public String config_marker_icon; + + @LangMessage + public TranslatedMessage lang_layer_label; + + @LangMessage + public TranslatedMessage lang_marker_label; + + private BedtimeDynmapLayerDelegate delegate = null; + + public BedtimeDynmapLayer(final Context context) { + super( + context.group( + "dynmap", + "Enable dynmap integration. Player spawnpoints (beds) will then be shown on a separate dynmap layer." + ) + ); + } + + public void delayed_on_enable() { + final var plugin = get_module().getServer().getPluginManager().getPlugin("dynmap"); + if (plugin == null) { + return; + } + + delegate = new BedtimeDynmapLayerDelegate(this); + delegate.on_enable(plugin); + } + + @Override + public void on_enable() { + schedule_next_tick(this::delayed_on_enable); + } + + @Override + public void on_disable() { + if (delegate != null) { + delegate.on_disable(); + delegate = null; + } + } + + public void update_marker(final OfflinePlayer player) { + if (delegate != null) { + delegate.update_marker(player); + } + } + + public void remove_marker(final UUID player_id) { + if (delegate != null) { + delegate.remove_marker(player_id); + } + } + + public void remove_marker(final String marker_id) { + if (delegate != null) { + delegate.remove_marker(marker_id); + } + } + + public void update_all_markers() { + if (delegate != null) { + delegate.update_all_markers(); + } + } } diff --git a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayerDelegate.java b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayerDelegate.java index 3c8a61902..1e9c80ccd 100644 --- a/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayerDelegate.java +++ b/vane-bedtime/src/main/java/org/oddlama/vane/bedtime/BedtimeDynmapLayerDelegate.java @@ -14,162 +14,165 @@ public class BedtimeDynmapLayerDelegate { - private final BedtimeDynmapLayer parent; - - private DynmapCommonAPI dynmap_api = null; - private MarkerAPI marker_api = null; - private boolean dynmap_enabled = false; - private MarkerSet marker_set = null; - private MarkerIcon marker_icon = null; - - public BedtimeDynmapLayerDelegate(final BedtimeDynmapLayer parent) { - this.parent = parent; - } - - public Bedtime get_module() { - return parent.get_module(); - } - - public void on_enable(final Plugin plugin) { - try { - DynmapCommonAPIListener.register(new DynmapCommonAPIListener() { - - @Override - public void apiEnabled(DynmapCommonAPI api) { - dynmap_api = api; - marker_api = dynmap_api.getMarkerAPI(); - } - - }); - - } catch (Exception e) { - get_module().log.log(Level.WARNING, "Error while enabling dynmap integration!", e); - return; - } - - if (marker_api == null) { - return; - } - - get_module().log.info("Enabling dynmap integration"); - dynmap_enabled = true; - create_or_load_layer(); - } - - public void on_disable() { - if (!dynmap_enabled) { - return; - } - - get_module().log.info("Disabling dynmap integration"); - dynmap_enabled = false; - dynmap_api = null; - marker_api = null; - } - - private void create_or_load_layer() { - // Create or retrieve layer - marker_set = marker_api.getMarkerSet(BedtimeDynmapLayer.LAYER_ID); - if (marker_set == null) { - marker_set = - marker_api.createMarkerSet(BedtimeDynmapLayer.LAYER_ID, parent.lang_layer_label.str(), null, false); - } - - if (marker_set == null) { - get_module().log.severe("Failed to create dynmap bedtime marker set!"); - return; - } - - // Update attributes - marker_set.setMarkerSetLabel(parent.lang_layer_label.str()); - marker_set.setLayerPriority(parent.config_layer_priority); - marker_set.setHideByDefault(parent.config_layer_hide); - - // Load marker - marker_icon = marker_api.getMarkerIcon(parent.config_marker_icon); - if (marker_icon == null) { - get_module().log.severe("Failed to load dynmap bedtime marker icon!"); - return; - } - - // Initial update - update_all_markers(); - } - - private String id_for(final UUID player_id) { - return player_id.toString(); - } - - private String id_for(final OfflinePlayer player) { - return id_for(player.getUniqueId()); - } - - public boolean update_marker(final OfflinePlayer player) { - if (!dynmap_enabled) { - return false; - } - - final var loc = player.getRespawnLocation(); - if (loc == null) { - return false; - } - - final var world_name = loc.getWorld().getName(); - final var marker_id = id_for(player); - final var marker_label = parent.lang_marker_label.str(player.getName()); - - marker_set.createMarker( - marker_id, - marker_label, - world_name, - loc.getX(), - loc.getY(), - loc.getZ(), - marker_icon, - false - ); - return true; - } - - public void remove_marker(final UUID player_id) { - remove_marker(id_for(player_id)); - } - - public void remove_marker(final String marker_id) { - if (!dynmap_enabled || marker_id == null) { - return; - } - - remove_marker(marker_set.findMarker(marker_id)); - } - - public void remove_marker(final Marker marker) { - if (!dynmap_enabled || marker == null) { - return; - } - - marker.deleteMarker(); - } - - public void update_all_markers() { - if (!dynmap_enabled) { - return; - } - - // Update all existing - final var id_set = new HashSet(); - for (final var player : get_module().get_offline_players_with_valid_name()) { - if (update_marker(player)) { - id_set.add(id_for(player)); - } - } - - // Remove orphaned - for (final var marker : marker_set.getMarkers()) { - final var id = marker.getMarkerID(); - if (id != null && !id_set.contains(id)) { - remove_marker(marker); - } - } - } + private final BedtimeDynmapLayer parent; + + private DynmapCommonAPI dynmap_api = null; + private MarkerAPI marker_api = null; + private boolean dynmap_enabled = false; + private MarkerSet marker_set = null; + private MarkerIcon marker_icon = null; + + public BedtimeDynmapLayerDelegate(final BedtimeDynmapLayer parent) { + this.parent = parent; + } + + public Bedtime get_module() { + return parent.get_module(); + } + + public void on_enable(final Plugin plugin) { + try { + DynmapCommonAPIListener.register( + new DynmapCommonAPIListener() { + @Override + public void apiEnabled(DynmapCommonAPI api) { + dynmap_api = api; + marker_api = dynmap_api.getMarkerAPI(); + } + } + ); + } catch (Exception e) { + get_module().log.log(Level.WARNING, "Error while enabling dynmap integration!", e); + return; + } + + if (marker_api == null) { + return; + } + + get_module().log.info("Enabling dynmap integration"); + dynmap_enabled = true; + create_or_load_layer(); + } + + public void on_disable() { + if (!dynmap_enabled) { + return; + } + + get_module().log.info("Disabling dynmap integration"); + dynmap_enabled = false; + dynmap_api = null; + marker_api = null; + } + + private void create_or_load_layer() { + // Create or retrieve layer + marker_set = marker_api.getMarkerSet(BedtimeDynmapLayer.LAYER_ID); + if (marker_set == null) { + marker_set = marker_api.createMarkerSet( + BedtimeDynmapLayer.LAYER_ID, + parent.lang_layer_label.str(), + null, + false + ); + } + + if (marker_set == null) { + get_module().log.severe("Failed to create dynmap bedtime marker set!"); + return; + } + + // Update attributes + marker_set.setMarkerSetLabel(parent.lang_layer_label.str()); + marker_set.setLayerPriority(parent.config_layer_priority); + marker_set.setHideByDefault(parent.config_layer_hide); + + // Load marker + marker_icon = marker_api.getMarkerIcon(parent.config_marker_icon); + if (marker_icon == null) { + get_module().log.severe("Failed to load dynmap bedtime marker icon!"); + return; + } + + // Initial update + update_all_markers(); + } + + private String id_for(final UUID player_id) { + return player_id.toString(); + } + + private String id_for(final OfflinePlayer player) { + return id_for(player.getUniqueId()); + } + + public boolean update_marker(final OfflinePlayer player) { + if (!dynmap_enabled) { + return false; + } + + final var loc = player.getRespawnLocation(); + if (loc == null) { + return false; + } + + final var world_name = loc.getWorld().getName(); + final var marker_id = id_for(player); + final var marker_label = parent.lang_marker_label.str(player.getName()); + + marker_set.createMarker( + marker_id, + marker_label, + world_name, + loc.getX(), + loc.getY(), + loc.getZ(), + marker_icon, + false + ); + return true; + } + + public void remove_marker(final UUID player_id) { + remove_marker(id_for(player_id)); + } + + public void remove_marker(final String marker_id) { + if (!dynmap_enabled || marker_id == null) { + return; + } + + remove_marker(marker_set.findMarker(marker_id)); + } + + public void remove_marker(final Marker marker) { + if (!dynmap_enabled || marker == null) { + return; + } + + marker.deleteMarker(); + } + + public void update_all_markers() { + if (!dynmap_enabled) { + return; + } + + // Update all existing + final var id_set = new HashSet(); + for (final var player : get_module().get_offline_players_with_valid_name()) { + if (update_marker(player)) { + id_set.add(id_for(player)); + } + } + + // Remove orphaned + for (final var marker : marker_set.getMarkers()) { + final var id = marker.getMarkerID(); + if (id != null && !id_set.contains(id)) { + remove_marker(marker); + } + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/Core.java b/vane-core/src/main/java/org/oddlama/vane/core/Core.java index c04a7c724..a6abb7084 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/Core.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/Core.java @@ -12,7 +12,13 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Level; - +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.core.Holder; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.EntityType; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerJoinEvent; @@ -40,237 +46,237 @@ import org.oddlama.vane.core.resourcepack.ResourcePackDistributor; import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.minecraft.core.Holder; -import net.minecraft.core.MappedRegistry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.world.entity.EntityType; - @VaneModule(name = "core", bstats = 8637, config_version = 6, lang_version = 4, storage_version = 1) public class Core extends Module { - /** Use sparingly. */ - private static Core INSTANCE = null; - public static Core instance() { - return INSTANCE; - } - - public EnchantmentManager enchantment_manager; - private CustomModelDataRegistry model_data_registry; - private CustomItemRegistry item_registry; - - @ConfigBoolean(def = true, desc = "Allow loading of player heads in relevant menus. Disabling this will show all player heads using the Steve skin, which may perform better on low-performance servers and clients.") - public boolean config_player_heads_in_menus; - - @LangMessage - public TranslatedMessage lang_command_not_a_player; - - @LangMessage - public TranslatedMessage lang_command_permission_denied; - - @LangMessage - public TranslatedMessage lang_invalid_time_format; - - // Module registry - private SortedSet> vane_modules = new TreeSet<>((a, b) -> a.get_name().compareTo(b.get_name())); - - public final ResourcePackDistributor resource_pack_distributor; - - public void register_module(Module module) { - vane_modules.add(module); - } - - public void unregister_module(Module module) { - vane_modules.remove(module); - } - - public SortedSet> get_modules() { - return Collections.unmodifiableSortedSet(vane_modules); - } - - // Vane global command catch-all permission - public Permission permission_command_catchall = new Permission( - "vane.*.commands.*", - "Allow access to all vane commands (ONLY FOR ADMINS!)", - PermissionDefault.FALSE - ); - - public MenuManager menu_manager; - - // core-config - @ConfigBoolean( - def = true, - desc = "Let the client translate messages using the generated resource pack. This allows every player to select their preferred language, and all plugin messages will also be translated. Disabling this won't allow you to skip generating the resource pack, as it will be needed for custom item textures." - ) - public boolean config_client_side_translations; - - @ConfigBoolean(def = true, desc = "Send update notices to OPed player when a new version of vane is available.") - public boolean config_update_notices; - - public String current_version = null; - public String latest_version = null; - - public Core() { - if (INSTANCE != null) { - throw new IllegalStateException("Cannot instanciate Core twice."); - } - INSTANCE = this; - - // Create global command catch-all permission - register_permission(permission_command_catchall); - - // Allow registration of new enchantments and entities - unfreeze_registries(); - - // Components - enchantment_manager = new EnchantmentManager(this); - new HeadLibrary(this); - new AuthMultiplexer(this); - new LootChestProtector(this); - new VanillaFunctionalityInhibitor(this); - new DurabilityManager(this); - new org.oddlama.vane.core.commands.Vane(this); - new org.oddlama.vane.core.commands.CustomItem(this); - new org.oddlama.vane.core.commands.Enchant(this); - menu_manager = new MenuManager(this); - resource_pack_distributor = new ResourcePackDistributor(this); - new CommandHider(this); - model_data_registry = new CustomModelDataRegistry(); - item_registry = new CustomItemRegistry(); - new ExistingItemConverter(this); - } - - @Override - public void on_enable() { - if (config_update_notices) { - // Now, and every hour after that, check if a new version is available. - // OPs will get a message about this when they join. - schedule_task_timer(this::check_for_update, 1l, ms_to_ticks(2 * 60l * 60l * 1000l)); - } - } - - public void unfreeze_registries() { - // NOTE: MAGIC VALUES! Introduced for 1.18.2 when registries were frozen. Sad, no workaround at the time. - try { - // Make relevant fields accessible - final var frozen = MappedRegistry.class.getDeclaredField("l" /* frozen */); - frozen.setAccessible(true); - final var intrusive_holder_cache = MappedRegistry.class.getDeclaredField("m" /* unregisteredIntrusiveHolders (1.19.3+), intrusiveHolderCache (until 1.19.2) */); - intrusive_holder_cache.setAccessible(true); - - // Unfreeze required registries - frozen.set(BuiltInRegistries.ENTITY_TYPE, false); - intrusive_holder_cache.set(BuiltInRegistries.ENTITY_TYPE, new IdentityHashMap, Holder.Reference>>()); - // Since 1.20.2 this is also needed for enchantments: - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); - } - } - - @Override - public void on_disable() { - } - - public File generate_resource_pack() { - try { - var file = new File("vane-resource-pack.zip"); - // TODO pack version number here. warn if merging lower. - var pack = new ResourcePackGenerator(); - pack.set_description("Vane plugin resource pack"); - pack.set_icon_png(getResource("pack.png")); - - for (var m : vane_modules) { - m.generate_resource_pack(pack); - } - - for (final var custom_item : item_registry().all()) { - custom_item.addResources(pack); - } - - pack.write(file); - return file; - } catch (Exception e) { - log.log(Level.SEVERE, "Error while generating resourcepack", e); - return null; - } - } - - public void for_all_module_components(final Consumer1> f) { - for (var m : vane_modules) { - m.for_each_module_component(f); - } - } - - public CustomItemRegistry item_registry() { - return item_registry; - } - - public CustomModelDataRegistry model_data_registry() { - return model_data_registry; - } - - public void check_for_update() { - if (current_version == null) { - try { - Properties properties = new Properties(); - properties.load(Core.class.getResourceAsStream("/vane-core.properties")); - current_version = "v" + properties.getProperty("version"); - } catch (IOException e) { - log.severe("Could not load current version from included properties file: " + e); - return; - } - } - - try { - final var json = read_json_from_url("https://api.github.com/repos/oddlama/vane/releases/latest"); - latest_version = json.getString("tag_name"); - if (latest_version != null && !latest_version.equals(current_version)) { - log.warning( - "A newer version of vane is available online! (current=" + - current_version + - ", new=" + - latest_version + - ")" - ); - log.warning("Please update as soon as possible to get the latest features and fixes."); - log.warning("Get the latest release here: https://github.com/oddlama/vane/releases/latest"); - } - } catch (IOException | JSONException | URISyntaxException e) { - log.warning("Could not check for updates: " + e); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) - public void on_player_join_send_update_notice(PlayerJoinEvent event) { - if (!config_update_notices) { - return; - } - - // Send an update message if a new version is available and player is OP. - if (latest_version != null && !latest_version.equals(current_version) && event.getPlayer().isOp()) { - // This message is intentionally not translated to ensure it will - // be displayed correctly and so that everyone understands it. - event - .getPlayer() - .sendMessage( - Component - .text("A new version of vane ", NamedTextColor.GREEN) - .append(Component.text("(" + latest_version + ")", NamedTextColor.AQUA)) - .append(Component.text(" is available!", NamedTextColor.GREEN)) - ); - event - .getPlayer() - .sendMessage( - Component.text("Please update soon to get the latest features.", NamedTextColor.GREEN) - ); - event - .getPlayer() - .sendMessage( - Component - .text("Click here to go to the download page", NamedTextColor.AQUA) - .clickEvent(ClickEvent.openUrl("https://github.com/oddlama/vane/releases/latest")) - ); - } - } + + /** Use sparingly. */ + private static Core INSTANCE = null; + + public static Core instance() { + return INSTANCE; + } + + public EnchantmentManager enchantment_manager; + private CustomModelDataRegistry model_data_registry; + private CustomItemRegistry item_registry; + + @ConfigBoolean( + def = true, + desc = "Allow loading of player heads in relevant menus. Disabling this will show all player heads using the Steve skin, which may perform better on low-performance servers and clients." + ) + public boolean config_player_heads_in_menus; + + @LangMessage + public TranslatedMessage lang_command_not_a_player; + + @LangMessage + public TranslatedMessage lang_command_permission_denied; + + @LangMessage + public TranslatedMessage lang_invalid_time_format; + + // Module registry + private SortedSet> vane_modules = new TreeSet<>((a, b) -> a.get_name().compareTo(b.get_name())); + + public final ResourcePackDistributor resource_pack_distributor; + + public void register_module(Module module) { + vane_modules.add(module); + } + + public void unregister_module(Module module) { + vane_modules.remove(module); + } + + public SortedSet> get_modules() { + return Collections.unmodifiableSortedSet(vane_modules); + } + + // Vane global command catch-all permission + public Permission permission_command_catchall = new Permission( + "vane.*.commands.*", + "Allow access to all vane commands (ONLY FOR ADMINS!)", + PermissionDefault.FALSE + ); + + public MenuManager menu_manager; + + // core-config + @ConfigBoolean( + def = true, + desc = "Let the client translate messages using the generated resource pack. This allows every player to select their preferred language, and all plugin messages will also be translated. Disabling this won't allow you to skip generating the resource pack, as it will be needed for custom item textures." + ) + public boolean config_client_side_translations; + + @ConfigBoolean(def = true, desc = "Send update notices to OPed player when a new version of vane is available.") + public boolean config_update_notices; + + public String current_version = null; + public String latest_version = null; + + public Core() { + if (INSTANCE != null) { + throw new IllegalStateException("Cannot instanciate Core twice."); + } + INSTANCE = this; + + // Create global command catch-all permission + register_permission(permission_command_catchall); + + // Allow registration of new enchantments and entities + unfreeze_registries(); + + // Components + enchantment_manager = new EnchantmentManager(this); + new HeadLibrary(this); + new AuthMultiplexer(this); + new LootChestProtector(this); + new VanillaFunctionalityInhibitor(this); + new DurabilityManager(this); + new org.oddlama.vane.core.commands.Vane(this); + new org.oddlama.vane.core.commands.CustomItem(this); + new org.oddlama.vane.core.commands.Enchant(this); + menu_manager = new MenuManager(this); + resource_pack_distributor = new ResourcePackDistributor(this); + new CommandHider(this); + model_data_registry = new CustomModelDataRegistry(); + item_registry = new CustomItemRegistry(); + new ExistingItemConverter(this); + } + + @Override + public void on_enable() { + if (config_update_notices) { + // Now, and every hour after that, check if a new version is available. + // OPs will get a message about this when they join. + schedule_task_timer(this::check_for_update, 1l, ms_to_ticks(2 * 60l * 60l * 1000l)); + } + } + + public void unfreeze_registries() { + // NOTE: MAGIC VALUES! Introduced for 1.18.2 when registries were frozen. Sad, no workaround + // at the time. + try { + // Make relevant fields accessible + final var frozen = MappedRegistry.class.getDeclaredField("l"/* frozen */); + frozen.setAccessible(true); + final var intrusive_holder_cache = + MappedRegistry.class.getDeclaredField( + "m"/* unregisteredIntrusiveHolders (1.19.3+), intrusiveHolderCache (until 1.19.2) */ + ); + intrusive_holder_cache.setAccessible(true); + + // Unfreeze required registries + frozen.set(BuiltInRegistries.ENTITY_TYPE, false); + intrusive_holder_cache.set( + BuiltInRegistries.ENTITY_TYPE, + new IdentityHashMap, Holder.Reference>>() + ); + // Since 1.20.2 this is also needed for enchantments: + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + @Override + public void on_disable() {} + + public File generate_resource_pack() { + try { + var file = new File("vane-resource-pack.zip"); + // TODO pack version number here. warn if merging lower. + var pack = new ResourcePackGenerator(); + pack.set_description("Vane plugin resource pack"); + pack.set_icon_png(getResource("pack.png")); + + for (var m : vane_modules) { + m.generate_resource_pack(pack); + } + + for (final var custom_item : item_registry().all()) { + custom_item.addResources(pack); + } + + pack.write(file); + return file; + } catch (Exception e) { + log.log(Level.SEVERE, "Error while generating resourcepack", e); + return null; + } + } + + public void for_all_module_components(final Consumer1> f) { + for (var m : vane_modules) { + m.for_each_module_component(f); + } + } + + public CustomItemRegistry item_registry() { + return item_registry; + } + + public CustomModelDataRegistry model_data_registry() { + return model_data_registry; + } + + public void check_for_update() { + if (current_version == null) { + try { + Properties properties = new Properties(); + properties.load(Core.class.getResourceAsStream("/vane-core.properties")); + current_version = "v" + properties.getProperty("version"); + } catch (IOException e) { + log.severe("Could not load current version from included properties file: " + e); + return; + } + } + + try { + final var json = read_json_from_url("https://api.github.com/repos/oddlama/vane/releases/latest"); + latest_version = json.getString("tag_name"); + if (latest_version != null && !latest_version.equals(current_version)) { + log.warning( + "A newer version of vane is available online! (current=" + + current_version + + ", new=" + + latest_version + + ")" + ); + log.warning("Please update as soon as possible to get the latest features and fixes."); + log.warning("Get the latest release here: https://github.com/oddlama/vane/releases/latest"); + } + } catch (IOException | JSONException | URISyntaxException e) { + log.warning("Could not check for updates: " + e); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) + public void on_player_join_send_update_notice(PlayerJoinEvent event) { + if (!config_update_notices) { + return; + } + + // Send an update message if a new version is available and player is OP. + if (latest_version != null && !latest_version.equals(current_version) && event.getPlayer().isOp()) { + // This message is intentionally not translated to ensure it will + // be displayed correctly and so that everyone understands it. + event + .getPlayer() + .sendMessage( + Component.text("A new version of vane ", NamedTextColor.GREEN) + .append(Component.text("(" + latest_version + ")", NamedTextColor.AQUA)) + .append(Component.text(" is available!", NamedTextColor.GREEN)) + ); + event + .getPlayer() + .sendMessage(Component.text("Please update soon to get the latest features.", NamedTextColor.GREEN)); + event + .getPlayer() + .sendMessage( + Component.text("Click here to go to the download page", NamedTextColor.AQUA).clickEvent( + ClickEvent.openUrl("https://github.com/oddlama/vane/releases/latest") + ) + ); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/Listener.java b/vane-core/src/main/java/org/oddlama/vane/core/Listener.java index 0c8ef0fe8..24a684abd 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/Listener.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/Listener.java @@ -6,17 +6,17 @@ public class Listener> extends ModuleComponent implements org.bukkit.event.Listener { - public Listener(Context context) { - super(context); - } + public Listener(Context context) { + super(context); + } - @Override - protected void on_enable() { - get_module().register_listener(this); - } + @Override + protected void on_enable() { + get_module().register_listener(this); + } - @Override - protected void on_disable() { - get_module().unregister_listener(this); - } + @Override + protected void on_disable() { + get_module().unregister_listener(this); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/LootTable.java b/vane-core/src/main/java/org/oddlama/vane/core/LootTable.java index c3b054e0f..15b13eeb6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/LootTable.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/LootTable.java @@ -9,79 +9,77 @@ import org.oddlama.vane.core.functional.Consumer2; public class LootTable { - private Map> possible_loot = new HashMap<>(); - - public LootTable() {} - - public LootTable put(final NamespacedKey key, final LootTableEntry entry) { - possible_loot.put(key, List.of(entry)); - return this; - } - - public LootTable put(final NamespacedKey key, final List entries) { - possible_loot.put(key, entries); - return this; - } - - public LootTable remove(final NamespacedKey key) { - possible_loot.remove(key); - return this; - } - - public Map> possible_loot() { - return possible_loot; - } - - public void generate_loot(final List output, final Random random) { - for (final var set : possible_loot.values()) { - for (final var loot : set) { - if (loot.evaluate_chance(random)) { - loot.add_sample(output, random); - } - } - } - } - - public static class LootTableEntry { - - public double chance; - public Consumer2, Random> generator; - - public LootTableEntry(int rarity_expected_chests, final ItemStack item) { - this(1.0 / rarity_expected_chests, item.clone(), 1, 1); - } - - public LootTableEntry(int rarity_expected_chests, final ItemStack item, int amount_min, int amount_max) { - this(1.0 / rarity_expected_chests, item.clone(), amount_min, amount_max); - } - - public LootTableEntry(double chance, final ItemStack item, int amount_min, int amount_max) { - this( - chance, - (list, random) -> { - final var i = item.clone(); - final var amount = random.nextInt(amount_max - amount_min + 1) + amount_min; - if (amount < 1) { - return; - } - - i.setAmount(amount); - list.add(i); - } - ); - } - - public LootTableEntry(double chance, Consumer2, Random> generator) { - this.chance = chance; - this.generator = generator; - } - - public void add_sample(final List items, final Random random) { - generator.apply(items, random); - } - - public boolean evaluate_chance(Random random) { - return random.nextDouble() < chance; - } - } + + private Map> possible_loot = new HashMap<>(); + + public LootTable() {} + + public LootTable put(final NamespacedKey key, final LootTableEntry entry) { + possible_loot.put(key, List.of(entry)); + return this; + } + + public LootTable put(final NamespacedKey key, final List entries) { + possible_loot.put(key, entries); + return this; + } + + public LootTable remove(final NamespacedKey key) { + possible_loot.remove(key); + return this; + } + + public Map> possible_loot() { + return possible_loot; + } + + public void generate_loot(final List output, final Random random) { + for (final var set : possible_loot.values()) { + for (final var loot : set) { + if (loot.evaluate_chance(random)) { + loot.add_sample(output, random); + } + } + } + } + + public static class LootTableEntry { + + public double chance; + public Consumer2, Random> generator; + + public LootTableEntry(int rarity_expected_chests, final ItemStack item) { + this(1.0 / rarity_expected_chests, item.clone(), 1, 1); + } + + public LootTableEntry(int rarity_expected_chests, final ItemStack item, int amount_min, int amount_max) { + this(1.0 / rarity_expected_chests, item.clone(), amount_min, amount_max); + } + + public LootTableEntry(double chance, final ItemStack item, int amount_min, int amount_max) { + this(chance, (list, random) -> { + final var i = item.clone(); + final var amount = random.nextInt(amount_max - amount_min + 1) + amount_min; + if (amount < 1) { + return; + } + + i.setAmount(amount); + list.add(i); + }); + } + + public LootTableEntry(double chance, Consumer2, Random> generator) { + this.chance = chance; + this.generator = generator; + } + + public void add_sample(final List items, final Random random) { + generator.apply(items, random); + } + + public boolean evaluate_chance(Random random) { + return random.nextDouble() < chance; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/YamlLoadException.java b/vane-core/src/main/java/org/oddlama/vane/core/YamlLoadException.java index 7f69d599e..fa2388113 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/YamlLoadException.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/YamlLoadException.java @@ -5,43 +5,43 @@ @SuppressWarnings("serial") public class YamlLoadException extends Exception { - public YamlLoadException(String message) { - super(message); - } - - public YamlLoadException() { - super(); - } - - public YamlLoadException(String message, Throwable cause) { - super(message, cause); - } - - public YamlLoadException(Throwable cause) { - super(cause); - } - - protected YamlLoadException( - String message, - Throwable cause, - boolean enableSuppression, - boolean writableStackTrace - ) { - super(message, cause, enableSuppression, writableStackTrace); - } - - public static class Lang extends YamlLoadException { - - public final LangField langField; - - @Override - public String getMessage() { - return "[" + this.langField.toString() + "] " + super.getMessage(); - } - - public Lang(String message, LangField erroredField) { - super(message); - this.langField = erroredField; - } - } + public YamlLoadException(String message) { + super(message); + } + + public YamlLoadException() { + super(); + } + + public YamlLoadException(String message, Throwable cause) { + super(message, cause); + } + + public YamlLoadException(Throwable cause) { + super(cause); + } + + protected YamlLoadException( + String message, + Throwable cause, + boolean enableSuppression, + boolean writableStackTrace + ) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public static class Lang extends YamlLoadException { + + public final LangField langField; + + @Override + public String getMessage() { + return "[" + this.langField.toString() + "] " + super.getMessage(); + } + + public Lang(String message, LangField erroredField) { + super(message); + this.langField = erroredField; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/Command.java b/vane-core/src/main/java/org/oddlama/vane/core/command/Command.java index 6468dc36a..bc9679eb1 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/Command.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/Command.java @@ -4,6 +4,11 @@ import static io.papermc.paper.command.brigadier.Commands.literal; import static org.oddlama.vane.util.ArrayUtil.prepend; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.LiteralCommandNode; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import java.util.Collections; import java.util.List; import org.bukkit.command.CommandSender; @@ -21,204 +26,197 @@ import org.oddlama.vane.core.module.Module; import org.oddlama.vane.core.module.ModuleComponent; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.tree.LiteralCommandNode; - -import io.papermc.paper.command.brigadier.CommandSourceStack; -import io.papermc.paper.command.brigadier.Commands; - @VaneCommand public abstract class Command> extends ModuleComponent { - public class BukkitCommand extends org.bukkit.command.Command implements PluginIdentifiableCommand { - - public BukkitCommand(String name) { - super(name); - setPermission(Command.this.permission.getName()); - } - - @Override - public String getUsage() { - return Command.this.lang_usage.str("§7/§3" + name); - } - - @Override - public String getDescription() { - return Command.this.lang_description.str(); - } - - @Override - public Plugin getPlugin() { - return Command.this.get_module(); - } - - @Override - public boolean execute(CommandSender sender, String alias, String[] args) { - System.out.println("exec " + alias + " from " + sender); - // Pre-check permission - if (!sender.hasPermission(Command.this.permission)) { - get_module().core.lang_command_permission_denied.send(sender); - System.out.println("no perms!"); - return true; - } - - // Ambiguous matches will always execute the - // first chain based on definition order. - try { - return root_param.check_accept(sender, prepend(args, alias), 0).apply(Command.this, sender); - } catch (Exception e) { - sender.sendMessage( - "§cAn unexpected error occurred. Please examine the console log and/or notify a server administrator." - ); - throw e; - } - } - - @Override - public List tabComplete(CommandSender sender, String alias, String[] args) - throws IllegalArgumentException { - // Don't allow information exfiltration! - if (!sender.hasPermission(getPermission())) { - return Collections.emptyList(); - } - - try { - return root_param.build_completions(sender, prepend(args, alias), 0); - } catch (Exception e) { - sender.sendMessage( - "§cAn unexpected error occurred. Please examine the console log and/or notify a server administrator." - ); - throw e; - } - } - } - - // Language - @LangMessage - public TranslatedMessage lang_usage; - - @LangMessage - public TranslatedMessage lang_description; - - @LangMessage - public TranslatedMessage lang_help; - - // Variables - private String name; - private Permission permission; - private BukkitCommand bukkit_command; - - // Root parameter - private AnyParam root_param; - - private LiteralArgumentBuilder brigadier_command; - private Aliases aliases; - - public Command(Context context) { - this(context, PermissionDefault.OP); - } - public Command(Context context, PermissionDefault permission_default) { - super(null); - // Make namespace - name = getClass().getAnnotation(Name.class).value(); - context = context.group("command_" + name, "Enable command " + name); - set_context(context); - - // Register permission - permission = - new Permission( - "vane." + get_module().get_name() + ".commands." + name, - "Allow access to /" + name, - permission_default - ); - get_module().register_permission(permission); - permission.addParent(get_module().permission_command_catchall_module, true); - permission.addParent(get_module().core.permission_command_catchall, true); - - // Always allow the console to execute commands - get_module().add_console_permission(permission); - - // Initialize root parameter - root_param = new AnyParam(this, "/" + get_name(), str -> str); - - // Create bukkit command - bukkit_command = new BukkitCommand(name); - bukkit_command.setLabel(name); - bukkit_command.setName(name); - - - aliases = getClass().getAnnotation(Aliases.class); - brigadier_command = Commands.literal(name); - if (aliases != null) { - bukkit_command.setAliases(List.of(aliases.value())); - } - } - - public BukkitCommand get_bukkit_command() { - return bukkit_command; - } - - public String get_name() { - return name; - } - - public String get_permission() { - return permission.getName(); - } - - public String get_prefix() { - return "vane:" + get_module().get_name(); - } - - public Param params() { - return root_param; - } - - public LiteralArgumentBuilder get_command_base() { - return brigadier_command; - } - - public LiteralCommandNode get_command() { - var cmd = get_command_base(); - var old_requirement = cmd.getRequirement(); - return cmd - .requires(stack -> stack.getSender().hasPermission(permission) && old_requirement.test(stack)) - .build(); - } - - public List get_aliases() { - if (aliases != null && aliases.value().length > 0) { - return List.of(aliases.value()); - } else { - return Collections.emptyList(); - } - - } - - @Override - protected void on_enable() { - get_module().register_command(this); - } - - @Override - protected void on_disable() { - get_module().unregister_command(this); - } - - public void print_help(CommandSender sender) { - lang_usage.send(sender, "§7/§3" + name); - lang_help.send(sender); - } - - public int print_help2(CommandContext ctx) { - lang_usage.send(ctx.getSource().getSender(), "§7/§3" + name); - lang_help.send(ctx.getSource().getSender()); - return com.mojang.brigadier.Command.SINGLE_SUCCESS; - } - - public LiteralArgumentBuilder help(){ - return literal("help").executes(ctx -> {print_help2(ctx); return SINGLE_SUCCESS;}); - } - + public class BukkitCommand extends org.bukkit.command.Command implements PluginIdentifiableCommand { + + public BukkitCommand(String name) { + super(name); + setPermission(Command.this.permission.getName()); + } + + @Override + public String getUsage() { + return Command.this.lang_usage.str("§7/§3" + name); + } + + @Override + public String getDescription() { + return Command.this.lang_description.str(); + } + + @Override + public Plugin getPlugin() { + return Command.this.get_module(); + } + + @Override + public boolean execute(CommandSender sender, String alias, String[] args) { + System.out.println("exec " + alias + " from " + sender); + // Pre-check permission + if (!sender.hasPermission(Command.this.permission)) { + get_module().core.lang_command_permission_denied.send(sender); + System.out.println("no perms!"); + return true; + } + + // Ambiguous matches will always execute the + // first chain based on definition order. + try { + return root_param.check_accept(sender, prepend(args, alias), 0).apply(Command.this, sender); + } catch (Exception e) { + sender.sendMessage( + "§cAn unexpected error occurred. Please examine the console log and/or notify a server administrator." + ); + throw e; + } + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) + throws IllegalArgumentException { + // Don't allow information exfiltration! + if (!sender.hasPermission(getPermission())) { + return Collections.emptyList(); + } + + try { + return root_param.build_completions(sender, prepend(args, alias), 0); + } catch (Exception e) { + sender.sendMessage( + "§cAn unexpected error occurred. Please examine the console log and/or notify a server administrator." + ); + throw e; + } + } + } + + // Language + @LangMessage + public TranslatedMessage lang_usage; + + @LangMessage + public TranslatedMessage lang_description; + + @LangMessage + public TranslatedMessage lang_help; + + // Variables + private String name; + private Permission permission; + private BukkitCommand bukkit_command; + + // Root parameter + private AnyParam root_param; + + private LiteralArgumentBuilder brigadier_command; + private Aliases aliases; + + public Command(Context context) { + this(context, PermissionDefault.OP); + } + + public Command(Context context, PermissionDefault permission_default) { + super(null); + // Make namespace + name = getClass().getAnnotation(Name.class).value(); + context = context.group("command_" + name, "Enable command " + name); + set_context(context); + + // Register permission + permission = new Permission( + "vane." + get_module().get_name() + ".commands." + name, + "Allow access to /" + name, + permission_default + ); + get_module().register_permission(permission); + permission.addParent(get_module().permission_command_catchall_module, true); + permission.addParent(get_module().core.permission_command_catchall, true); + + // Always allow the console to execute commands + get_module().add_console_permission(permission); + + // Initialize root parameter + root_param = new AnyParam(this, "/" + get_name(), str -> str); + + // Create bukkit command + bukkit_command = new BukkitCommand(name); + bukkit_command.setLabel(name); + bukkit_command.setName(name); + + aliases = getClass().getAnnotation(Aliases.class); + brigadier_command = Commands.literal(name); + if (aliases != null) { + bukkit_command.setAliases(List.of(aliases.value())); + } + } + + public BukkitCommand get_bukkit_command() { + return bukkit_command; + } + + public String get_name() { + return name; + } + + public String get_permission() { + return permission.getName(); + } + + public String get_prefix() { + return "vane:" + get_module().get_name(); + } + + public Param params() { + return root_param; + } + + public LiteralArgumentBuilder get_command_base() { + return brigadier_command; + } + + public LiteralCommandNode get_command() { + var cmd = get_command_base(); + var old_requirement = cmd.getRequirement(); + return cmd + .requires(stack -> stack.getSender().hasPermission(permission) && old_requirement.test(stack)) + .build(); + } + + public List get_aliases() { + if (aliases != null && aliases.value().length > 0) { + return List.of(aliases.value()); + } else { + return Collections.emptyList(); + } + } + + @Override + protected void on_enable() { + get_module().register_command(this); + } + + @Override + protected void on_disable() { + get_module().unregister_command(this); + } + + public void print_help(CommandSender sender) { + lang_usage.send(sender, "§7/§3" + name); + lang_help.send(sender); + } + + public int print_help2(CommandContext ctx) { + lang_usage.send(ctx.getSource().getSender(), "§7/§3" + name); + lang_help.send(ctx.getSource().getSender()); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + } + + public LiteralArgumentBuilder help() { + return literal("help").executes(ctx -> { + print_help2(ctx); + return SINGLE_SUCCESS; + }); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/Executor.java b/vane-core/src/main/java/org/oddlama/vane/core/command/Executor.java index 2cf20f94c..e362e91b4 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/Executor.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/Executor.java @@ -4,5 +4,5 @@ import org.bukkit.command.CommandSender; public interface Executor { - public boolean execute(Command command, CommandSender sender, List parsed_args); + public boolean execute(Command command, CommandSender sender, List parsed_args); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/Param.java b/vane-core/src/main/java/org/oddlama/vane/core/command/Param.java index 333c0538c..ceec45763 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/Param.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/Param.java @@ -2,11 +2,12 @@ import static org.oddlama.vane.util.StorageUtil.namespaced_key; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; - import org.bukkit.GameMode; import org.bukkit.OfflinePlayer; import org.bukkit.World; @@ -37,346 +38,360 @@ import org.oddlama.vane.core.functional.Function6; import org.oddlama.vane.core.module.Module; -import io.papermc.paper.registry.RegistryAccess; -import io.papermc.paper.registry.RegistryKey; - @SuppressWarnings("overloads") public interface Param { - public List get_params(); + public List get_params(); - public default boolean require_player(CommandSender sender) { - if (!(sender instanceof Player)) { - get_command().get_module().core.lang_command_not_a_player.send(sender); - return false; - } - - return true; - } + public default boolean require_player(CommandSender sender) { + if (!(sender instanceof Player)) { + get_command().get_module().core.lang_command_not_a_player.send(sender); + return false; + } + + return true; + } - public default boolean is_executor() { - return false; - } + public default boolean is_executor() { + return false; + } - public default void add_param(Param param) { - if (param.is_executor() && get_params().stream().anyMatch(p -> p.is_executor())) { - throw new RuntimeException("Cannot define multiple executors for the same parameter! This is a bug."); - } - get_params().add(param); - } + public default void add_param(Param param) { + if (param.is_executor() && get_params().stream().anyMatch(p -> p.is_executor())) { + throw new RuntimeException("Cannot define multiple executors for the same parameter! This is a bug."); + } + get_params().add(param); + } - public default void exec_player(Consumer1 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Consumer2 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Consumer3 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Consumer4 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Consumer5 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Consumer6 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Function1 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Function2 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Function3 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Function4 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Function5 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec_player(Function6 f) { - add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); - } - - public default void exec(Consumer1 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Consumer2 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Consumer3 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Consumer4 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Consumer5 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Consumer6 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Function1 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Function2 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Function3 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Function4 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Function5 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default void exec(Function6 f) { - add_param(new SentinelExecutorParam<>(get_command(), f)); - } - - public default Param any_string() { - return any("string", str -> str); - } - - public default AnyParam any(String argument_type, Function1 from_string) { - var p = new AnyParam<>(get_command(), argument_type, from_string); - add_param(p); - return p; - } - - public default FixedParam fixed(String fixed) { - return fixed(fixed, str -> str); - } - - public default FixedParam fixed(T fixed, Function1 to_string) { - var p = new FixedParam<>(get_command(), fixed, to_string); - add_param(p); - return p; - } - - public default Param choice(Collection choices) { - return choice("choice", choices, str -> str); - } - - public default ChoiceParam choice( - String argument_type, - Collection choices, - Function1 to_string) { - var p = new ChoiceParam<>(get_command(), argument_type, choices, to_string); - add_param(p); - return p; - } - - public default DynamicChoiceParam choice( - String argument_type, - Function1> choices, - Function2 to_string, - Function2 from_string) { - var p = new DynamicChoiceParam<>(get_command(), argument_type, choices, to_string, from_string); - add_param(p); - return p; - } - - public default DynamicChoiceParam> choose_module() { - return choice( - "module", - sender -> get_command().get_module().core.get_modules(), - (sender, m) -> m.get_name(), - (sender, str) -> get_command() - .get_module().core.get_modules() - .stream() - .filter(k -> k.get_name().equalsIgnoreCase(str)) - .findFirst() - .orElse(null)); - } - - public default DynamicChoiceParam choose_world() { - return choice( - "world", - sender -> get_command().get_module().getServer().getWorlds(), - (sender, w) -> w.getName().toLowerCase(), - (sender, str) -> get_command() - .get_module() - .getServer() - .getWorlds() - .stream() - .filter(w -> w.getName().equalsIgnoreCase(str)) - .findFirst() - .orElse(null)); - } - - public default DynamicChoiceParam choose_any_player() { - return choice( - "any_player", - sender -> get_command().get_module().get_offline_players_with_valid_name(), - (sender, p) -> p.getName(), - (sender, str) -> get_command() - .get_module() - .get_offline_players_with_valid_name() - .stream() - .filter(k -> k.getName().equalsIgnoreCase(str)) - .findFirst() - .orElse(null)); - } - - public default DynamicChoiceParam choose_online_player() { - return choice( - "online_player", - sender -> get_command().get_module().getServer().getOnlinePlayers(), - (sender, p) -> p.getName(), - (sender, str) -> get_command() - .get_module() - .getServer() - .getOnlinePlayers() - .stream() - .filter(k -> k.getName().equalsIgnoreCase(str)) - .findFirst() - .orElse(null)); - } - - // TODO (minor): Make choose_permission filter results based on the previously - // specified player. - public default DynamicChoiceParam choose_permission() { - return choice( - "permission", - sender -> get_command().get_module().getServer().getPluginManager().getPermissions(), - (sender, p) -> p.getName(), - (sender, str) -> get_command().get_module().getServer().getPluginManager().getPermission(str)); - } - - public default ChoiceParam choose_gamemode() { - return choice("gamemode", List.of(GameMode.values()), m -> m.name().toLowerCase()).ignore_case(); - } - - public default DynamicChoiceParam choose_enchantment() { - return choose_enchantment((sender, e) -> true); - } - - public default DynamicChoiceParam choose_enchantment( - final Function2 filter) { - return choice( - "enchantment", - sender -> RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).stream() - .filter(e -> filter.apply(sender, e)).collect(Collectors.toList()), - (sender, e) -> e.getKey().toString(), - (sender, str) -> { - var parts = str.split(":"); - if (parts.length != 2) { - return null; - } - var e = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT) - .get(namespaced_key(parts[0], parts[1])); - if (!filter.apply(sender, e)) { - return null; - } - return e; - }); - } - - public default CheckResult check_accept_delegate(CommandSender sender, String[] args, int offset) { - if (get_params().isEmpty()) { - throw new RuntimeException("Encountered parameter without sentinel! This is a bug."); - } - - // Delegate to children - var results = get_params() - .stream() - .map(p -> p.check_accept(sender, args, offset + 1)).toList(); - - // Return the first executor result, if any - for (var r : results) { - if (r.good()) { - return r; - } - } - - // Only retain errors from maximum depth - var max_depth = results.stream().map(r -> r.depth()).reduce(0, Integer::max); - - var errors = results - .stream() - .filter(r -> r.depth() == max_depth) - .map(ErrorCheckResult.class::cast) - .collect(Collectors.toList()); - - // If there is only a single max-depth sub-error, propagate it. - // Otherwise, combine multiple errors into new error. - if (errors.size() == 1) { - return errors.get(0); - } else { - return new CombinedErrorCheckResult(errors); - } - } - - public default CheckResult check_accept(CommandSender sender, String[] args, int offset) { - var result = check_parse(sender, args, offset); - if (!(result instanceof ParseCheckResult)) { - return result; - } - - var p = (ParseCheckResult) result; - return check_accept_delegate(sender, args, offset).prepend(p.argument_type(), p.parsed(), p.include_param()); - } - - public default List build_completions_delegate(CommandSender sender, String[] args, int offset) { - if (get_params().isEmpty()) { - throw new RuntimeException("Encountered parameter without sentinel! This is a bug."); - } - - // Delegate to children - return get_params() - .stream() - .map(p -> p.build_completions(sender, args, offset + 1)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - } - - public default List build_completions(CommandSender sender, String[] args, int offset) { - if (offset < args.length - 1) { - // We are not the last argument. - // Delegate completion to children if the param accepts the given arguments, - // return no completions if it doesn't - if (check_parse(sender, args, offset) instanceof ParseCheckResult) { - return build_completions_delegate(sender, args, offset); - } else { - return Collections.emptyList(); - } - } else { - // We are the parameter that needs to be completed. - // Offer (partial) completions if our depth is the completion depth - return completions_for(sender, args, offset); - } - } - - public List completions_for(CommandSender sender, String[] args, int offset); - - public CheckResult check_parse(CommandSender sender, String[] args, int offset); - - public Command get_command(); + public default void exec_player(Consumer1 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Consumer2 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Consumer3 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Consumer4 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Consumer5 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Consumer6 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Function1 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Function2 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Function3 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Function4 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Function5 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec_player(Function6 f) { + add_param(new SentinelExecutorParam<>(get_command(), f, this::require_player, i -> i == 0)); + } + + public default void exec(Consumer1 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Consumer2 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Consumer3 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Consumer4 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Consumer5 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Consumer6 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Function1 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Function2 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Function3 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Function4 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Function5 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default void exec(Function6 f) { + add_param(new SentinelExecutorParam<>(get_command(), f)); + } + + public default Param any_string() { + return any("string", str -> str); + } + + public default AnyParam any(String argument_type, Function1 from_string) { + var p = new AnyParam<>(get_command(), argument_type, from_string); + add_param(p); + return p; + } + + public default FixedParam fixed(String fixed) { + return fixed(fixed, str -> str); + } + + public default FixedParam fixed(T fixed, Function1 to_string) { + var p = new FixedParam<>(get_command(), fixed, to_string); + add_param(p); + return p; + } + + public default Param choice(Collection choices) { + return choice("choice", choices, str -> str); + } + + public default ChoiceParam choice( + String argument_type, + Collection choices, + Function1 to_string + ) { + var p = new ChoiceParam<>(get_command(), argument_type, choices, to_string); + add_param(p); + return p; + } + + public default DynamicChoiceParam choice( + String argument_type, + Function1> choices, + Function2 to_string, + Function2 from_string + ) { + var p = new DynamicChoiceParam<>(get_command(), argument_type, choices, to_string, from_string); + add_param(p); + return p; + } + + public default DynamicChoiceParam> choose_module() { + return choice( + "module", + sender -> get_command().get_module().core.get_modules(), + (sender, m) -> m.get_name(), + (sender, str) -> + get_command() + .get_module() + .core.get_modules() + .stream() + .filter(k -> k.get_name().equalsIgnoreCase(str)) + .findFirst() + .orElse(null) + ); + } + + public default DynamicChoiceParam choose_world() { + return choice( + "world", + sender -> get_command().get_module().getServer().getWorlds(), + (sender, w) -> w.getName().toLowerCase(), + (sender, str) -> + get_command() + .get_module() + .getServer() + .getWorlds() + .stream() + .filter(w -> w.getName().equalsIgnoreCase(str)) + .findFirst() + .orElse(null) + ); + } + + public default DynamicChoiceParam choose_any_player() { + return choice( + "any_player", + sender -> get_command().get_module().get_offline_players_with_valid_name(), + (sender, p) -> p.getName(), + (sender, str) -> + get_command() + .get_module() + .get_offline_players_with_valid_name() + .stream() + .filter(k -> k.getName().equalsIgnoreCase(str)) + .findFirst() + .orElse(null) + ); + } + + public default DynamicChoiceParam choose_online_player() { + return choice( + "online_player", + sender -> get_command().get_module().getServer().getOnlinePlayers(), + (sender, p) -> p.getName(), + (sender, str) -> + get_command() + .get_module() + .getServer() + .getOnlinePlayers() + .stream() + .filter(k -> k.getName().equalsIgnoreCase(str)) + .findFirst() + .orElse(null) + ); + } + + // TODO (minor): Make choose_permission filter results based on the previously + // specified player. + public default DynamicChoiceParam choose_permission() { + return choice( + "permission", + sender -> get_command().get_module().getServer().getPluginManager().getPermissions(), + (sender, p) -> p.getName(), + (sender, str) -> get_command().get_module().getServer().getPluginManager().getPermission(str) + ); + } + + public default ChoiceParam choose_gamemode() { + return choice("gamemode", List.of(GameMode.values()), m -> m.name().toLowerCase()).ignore_case(); + } + + public default DynamicChoiceParam choose_enchantment() { + return choose_enchantment((sender, e) -> true); + } + + public default DynamicChoiceParam choose_enchantment( + final Function2 filter + ) { + return choice( + "enchantment", + sender -> + RegistryAccess.registryAccess() + .getRegistry(RegistryKey.ENCHANTMENT) + .stream() + .filter(e -> filter.apply(sender, e)) + .collect(Collectors.toList()), + (sender, e) -> e.getKey().toString(), + (sender, str) -> { + var parts = str.split(":"); + if (parts.length != 2) { + return null; + } + var e = RegistryAccess.registryAccess() + .getRegistry(RegistryKey.ENCHANTMENT) + .get(namespaced_key(parts[0], parts[1])); + if (!filter.apply(sender, e)) { + return null; + } + return e; + } + ); + } + + public default CheckResult check_accept_delegate(CommandSender sender, String[] args, int offset) { + if (get_params().isEmpty()) { + throw new RuntimeException("Encountered parameter without sentinel! This is a bug."); + } + + // Delegate to children + var results = get_params().stream().map(p -> p.check_accept(sender, args, offset + 1)).toList(); + + // Return the first executor result, if any + for (var r : results) { + if (r.good()) { + return r; + } + } + + // Only retain errors from maximum depth + var max_depth = results.stream().map(r -> r.depth()).reduce(0, Integer::max); + + var errors = results + .stream() + .filter(r -> r.depth() == max_depth) + .map(ErrorCheckResult.class::cast) + .collect(Collectors.toList()); + + // If there is only a single max-depth sub-error, propagate it. + // Otherwise, combine multiple errors into new error. + if (errors.size() == 1) { + return errors.get(0); + } else { + return new CombinedErrorCheckResult(errors); + } + } + + public default CheckResult check_accept(CommandSender sender, String[] args, int offset) { + var result = check_parse(sender, args, offset); + if (!(result instanceof ParseCheckResult)) { + return result; + } + + var p = (ParseCheckResult) result; + return check_accept_delegate(sender, args, offset).prepend(p.argument_type(), p.parsed(), p.include_param()); + } + + public default List build_completions_delegate(CommandSender sender, String[] args, int offset) { + if (get_params().isEmpty()) { + throw new RuntimeException("Encountered parameter without sentinel! This is a bug."); + } + + // Delegate to children + return get_params() + .stream() + .map(p -> p.build_completions(sender, args, offset + 1)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + public default List build_completions(CommandSender sender, String[] args, int offset) { + if (offset < args.length - 1) { + // We are not the last argument. + // Delegate completion to children if the param accepts the given arguments, + // return no completions if it doesn't + if (check_parse(sender, args, offset) instanceof ParseCheckResult) { + return build_completions_delegate(sender, args, offset); + } else { + return Collections.emptyList(); + } + } else { + // We are the parameter that needs to be completed. + // Offer (partial) completions if our depth is the completion depth + return completions_for(sender, args, offset); + } + } + + public List completions_for(CommandSender sender, String[] args, int offset); + + public CheckResult check_parse(CommandSender sender, String[] args, int offset); + + public Command get_command(); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/CustomItemArgumentType.java b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/CustomItemArgumentType.java index 0c97ee2c7..53ebb74e2 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/CustomItemArgumentType.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/CustomItemArgumentType.java @@ -1,23 +1,20 @@ package org.oddlama.vane.core.command.argumentType; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.bukkit.NamespacedKey; -import org.jetbrains.annotations.NotNull; -import org.oddlama.vane.core.Core; -import org.oddlama.vane.core.item.api.CustomItem; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.MessageComponentSerializer; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; +import org.oddlama.vane.core.Core; +import org.oddlama.vane.core.item.api.CustomItem; public class CustomItemArgumentType implements CustomArgumentType.Converted { @@ -38,21 +35,26 @@ public static CustomItemArgumentType customItem(Core module) { @Override public @NotNull CustomItem convert(@NotNull NamespacedKey nativeType) throws CommandSyntaxException { - return this.module.item_registry().all().stream().filter(item -> item.key().equals(nativeType)).findFirst() - .orElseThrow(); + return this.module.item_registry() + .all() + .stream() + .filter(item -> item.key().equals(nativeType)) + .findFirst() + .orElseThrow(); } @Override - public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { Stream stream = this.module.item_registry().all().stream(); - if(!builder.getRemaining().isBlank()){ + if (!builder.getRemaining().isBlank()) { stream = stream.filter(item -> item.key().toString().contains(builder.getRemainingLowerCase())); } - stream.collect(Collectors.toMap(item -> item.key().toString(), CustomItem::displayName)) + stream + .collect(Collectors.toMap(item -> item.key().toString(), CustomItem::displayName)) .forEach((key, name) -> builder.suggest(key, MessageComponentSerializer.message().serialize(name))); return builder.buildFuture(); - } - } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/EnchantmentFilterArgumentType.java b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/EnchantmentFilterArgumentType.java index 989594f5d..e5e3cb233 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/EnchantmentFilterArgumentType.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/EnchantmentFilterArgumentType.java @@ -1,26 +1,22 @@ package org.oddlama.vane.core.command.argumentType; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.command.brigadier.argument.CustomArgumentType; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; public class EnchantmentFilterArgumentType implements CustomArgumentType.Converted { @@ -41,24 +37,26 @@ public static EnchantmentFilterArgumentType enchantmentFilter() { } @Override - public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { - + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { CommandSourceStack stack = (CommandSourceStack) context.getSource(); ItemStack item = ((Player) stack.getSender()).getInventory().getItemInMainHand(); - Stream compatibleEnchantments = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).stream(); - if(item.getType() != Material.BOOK && item.getType() != Material.ENCHANTED_BOOK) { + Stream compatibleEnchantments = RegistryAccess.registryAccess() + .getRegistry(RegistryKey.ENCHANTMENT) + .stream(); + if (item.getType() != Material.BOOK && item.getType() != Material.ENCHANTED_BOOK) { compatibleEnchantments = compatibleEnchantments.filter(ench -> ench.canEnchantItem(item)); } Stream stream = compatibleEnchantments.map(ench -> ench.getKey().asString()); - if(!builder.getRemaining().isBlank()) { + if (!builder.getRemaining().isBlank()) { stream = stream.filter(ench -> ench.contains(builder.getRemainingLowerCase())); } stream.forEach(builder::suggest); - return builder.buildFuture(); + return builder.buildFuture(); } - } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/ModuleArgumentType.java b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/ModuleArgumentType.java index eba21417a..dc8e78107 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/ModuleArgumentType.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/ModuleArgumentType.java @@ -1,21 +1,17 @@ package org.oddlama.vane.core.command.argumentType; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -import org.jetbrains.annotations.NotNull; -import org.oddlama.vane.core.Core; -import org.oddlama.vane.core.module.Module; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.argument.CustomArgumentType; - +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; +import org.oddlama.vane.core.Core; +import org.oddlama.vane.core.module.Module; public class ModuleArgumentType implements CustomArgumentType.Converted, String> { @@ -37,19 +33,25 @@ private ModuleArgumentType setCore(Core module) { @Override public @NotNull Module convert(@NotNull String nativeType) throws CommandSyntaxException { - return core.get_modules().stream().filter(module -> module.get_name().equalsIgnoreCase(nativeType)).findFirst().orElseThrow(); - + return core + .get_modules() + .stream() + .filter(module -> module.get_name().equalsIgnoreCase(nativeType)) + .findFirst() + .orElseThrow(); } @Override - public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { Stream stream = core.get_modules().stream().map(Module::get_name); - if(!builder.getRemaining().isBlank()) { + if (!builder.getRemaining().isBlank()) { stream = stream.filter(module -> module.contains(builder.getRemainingLowerCase())); } stream.forEach(module -> builder.suggest(module)); return builder.buildFuture(); - } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/OfflinePlayerArgumentType.java b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/OfflinePlayerArgumentType.java index dc7608ca8..c835dddd1 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/OfflinePlayerArgumentType.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/OfflinePlayerArgumentType.java @@ -1,23 +1,20 @@ package org.oddlama.vane.core.command.argumentType; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; -public class OfflinePlayerArgumentType implements CustomArgumentType.Converted{ +public class OfflinePlayerArgumentType implements CustomArgumentType.Converted { public static @NotNull OfflinePlayerArgumentType offlinePlayer() { return new OfflinePlayerArgumentType(); @@ -30,20 +27,22 @@ public class OfflinePlayerArgumentType implements CustomArgumentType.Converted @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { OfflinePlayer[] players = Bukkit.getOfflinePlayers(); Stream stream = Arrays.stream(players).map(p -> p.getName()).filter(p -> p != null); - if(!builder.getRemaining().isBlank()) { + if (!builder.getRemaining().isBlank()) { stream = stream.filter(player -> player.contains(builder.getRemaining())); } stream.forEach(builder::suggest); diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/TimeValueArgumentType.java b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/TimeValueArgumentType.java index 7c005fef7..38f991512 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/TimeValueArgumentType.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/TimeValueArgumentType.java @@ -1,20 +1,17 @@ package org.oddlama.vane.core.command.argumentType; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -import org.jetbrains.annotations.NotNull; -import org.oddlama.vane.core.command.enums.TimeValue; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; +import org.oddlama.vane.core.command.enums.TimeValue; public class TimeValueArgumentType implements CustomArgumentType.Converted { @@ -24,22 +21,24 @@ public static TimeValueArgumentType timeValue() { @Override public @NotNull ArgumentType getNativeType() { - return StringArgumentType.word(); + return StringArgumentType.word(); } @Override public @NotNull TimeValue convert(@NotNull String nativeType) throws CommandSyntaxException { - return TimeValue.valueOf(nativeType); + return TimeValue.valueOf(nativeType); } @Override - public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { Stream stream = Arrays.stream(TimeValue.values()).map(time -> time.name()); - if(!builder.getRemaining().isBlank()) { + if (!builder.getRemaining().isBlank()) { stream = stream.filter(timeName -> timeName.contains(builder.getRemaining())); } stream.forEach(builder::suggest); return builder.buildFuture(); - } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/WeatherArgumentType.java b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/WeatherArgumentType.java index bd0749716..0f6b6c55c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/WeatherArgumentType.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/argumentType/WeatherArgumentType.java @@ -1,22 +1,19 @@ package org.oddlama.vane.core.command.argumentType; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -import org.jetbrains.annotations.NotNull; -import org.oddlama.vane.core.command.enums.WeatherValue; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; +import org.oddlama.vane.core.command.enums.WeatherValue; -public class WeatherArgumentType implements CustomArgumentType.Converted{ +public class WeatherArgumentType implements CustomArgumentType.Converted { public static WeatherArgumentType weather() { return new WeatherArgumentType(); @@ -33,15 +30,15 @@ public static WeatherArgumentType weather() { } @Override - public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { Stream stream = Arrays.stream(WeatherValue.values()).map(time -> time.name()); - if(!builder.getRemaining().isBlank()) { + if (!builder.getRemaining().isBlank()) { stream = stream.filter(weatherName -> weatherName.contains(builder.getRemaining())); } stream.forEach(builder::suggest); return builder.buildFuture(); - } - - + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/check/CheckResult.java b/vane-core/src/main/java/org/oddlama/vane/core/command/check/CheckResult.java index c21cd354d..59d364fd5 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/check/CheckResult.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/check/CheckResult.java @@ -4,11 +4,11 @@ import org.oddlama.vane.core.command.Command; public interface CheckResult { - public int depth(); + public int depth(); - public boolean apply(Command command, CommandSender sender); + public boolean apply(Command command, CommandSender sender); - public CheckResult prepend(String argument_type, Object parsed_arg, boolean include); + public CheckResult prepend(String argument_type, Object parsed_arg, boolean include); - public boolean good(); + public boolean good(); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/check/CombinedErrorCheckResult.java b/vane-core/src/main/java/org/oddlama/vane/core/command/check/CombinedErrorCheckResult.java index 9e8fcfbff..4112ff161 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/check/CombinedErrorCheckResult.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/check/CombinedErrorCheckResult.java @@ -6,24 +6,24 @@ public class CombinedErrorCheckResult extends ErrorCheckResult { - private List errors; + private List errors; - public CombinedErrorCheckResult(List errors) { - super(errors.get(0).depth(), "§6could not match one of:§r"); - if (errors.size() < 2) { - throw new RuntimeException( - "Tried to create CombinedErrorCheckResult with less than 2 sub-errors! This is a bug." - ); - } - this.errors = errors; - } + public CombinedErrorCheckResult(List errors) { + super(errors.get(0).depth(), "§6could not match one of:§r"); + if (errors.size() < 2) { + throw new RuntimeException( + "Tried to create CombinedErrorCheckResult with less than 2 sub-errors! This is a bug." + ); + } + this.errors = errors; + } - @Override - public boolean apply(Command command, CommandSender sender, String indent) { - super.apply(command, sender, indent); - for (var err : errors) { - err.apply(command, sender, indent + " "); - } - return false; - } + @Override + public boolean apply(Command command, CommandSender sender, String indent) { + super.apply(command, sender, indent); + for (var err : errors) { + err.apply(command, sender, indent + " "); + } + return false; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/check/ErrorCheckResult.java b/vane-core/src/main/java/org/oddlama/vane/core/command/check/ErrorCheckResult.java index 4a95f0cff..eb20e4e77 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/check/ErrorCheckResult.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/check/ErrorCheckResult.java @@ -1,52 +1,51 @@ package org.oddlama.vane.core.command.check; +import java.util.Objects; import org.bukkit.command.CommandSender; import org.oddlama.vane.core.command.Command; -import java.util.Objects; - public class ErrorCheckResult implements CheckResult { - private int depth; - private String message; - private String arg_chain = ""; - - public ErrorCheckResult(int depth, String message) { - this.depth = depth; - this.message = message; - } - - @Override - public int depth() { - return depth; - } - - @Override - public boolean good() { - return false; - } - - @Override - public boolean apply(Command command, CommandSender sender) { - return apply(command, sender, ""); - } - - public boolean apply(Command command, CommandSender sender, String indent) { - var str = indent; - if (Objects.equals(indent, "")) { - str += "§cerror: "; - } - str += "§6"; - str += arg_chain; - str += message; - sender.sendMessage(str); - return false; - } - - @Override - public CheckResult prepend(String argument_type, Object parsed_arg, boolean include) { - // Save parsed arguments in an argument chain, and propagate error - arg_chain = "§3" + argument_type + "§6 → " + arg_chain; - return this; - } + private int depth; + private String message; + private String arg_chain = ""; + + public ErrorCheckResult(int depth, String message) { + this.depth = depth; + this.message = message; + } + + @Override + public int depth() { + return depth; + } + + @Override + public boolean good() { + return false; + } + + @Override + public boolean apply(Command command, CommandSender sender) { + return apply(command, sender, ""); + } + + public boolean apply(Command command, CommandSender sender, String indent) { + var str = indent; + if (Objects.equals(indent, "")) { + str += "§cerror: "; + } + str += "§6"; + str += arg_chain; + str += message; + sender.sendMessage(str); + return false; + } + + @Override + public CheckResult prepend(String argument_type, Object parsed_arg, boolean include) { + // Save parsed arguments in an argument chain, and propagate error + arg_chain = "§3" + argument_type + "§6 → " + arg_chain; + return this; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/check/ExecutorCheckResult.java b/vane-core/src/main/java/org/oddlama/vane/core/command/check/ExecutorCheckResult.java index 5c2dbf09f..0f4da8ed1 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/check/ExecutorCheckResult.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/check/ExecutorCheckResult.java @@ -7,34 +7,34 @@ public class ExecutorCheckResult implements CheckResult { - private int depth; - private Executor executor; - private ArrayList parsed_args = new ArrayList<>(); - - public ExecutorCheckResult(int depth, Executor executor) { - this.depth = depth; - this.executor = executor; - } - - @Override - public int depth() { - return depth; - } - - @Override - public boolean good() { - return true; - } - - public boolean apply(Command command, CommandSender sender) { - return executor.execute(command, sender, parsed_args); - } - - @Override - public CheckResult prepend(String argument_type, Object parsed_arg, boolean include) { - if (include) { - parsed_args.add(0, parsed_arg); - } - return this; - } + private int depth; + private Executor executor; + private ArrayList parsed_args = new ArrayList<>(); + + public ExecutorCheckResult(int depth, Executor executor) { + this.depth = depth; + this.executor = executor; + } + + @Override + public int depth() { + return depth; + } + + @Override + public boolean good() { + return true; + } + + public boolean apply(Command command, CommandSender sender) { + return executor.execute(command, sender, parsed_args); + } + + @Override + public CheckResult prepend(String argument_type, Object parsed_arg, boolean include) { + if (include) { + parsed_args.add(0, parsed_arg); + } + return this; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/check/ParseCheckResult.java b/vane-core/src/main/java/org/oddlama/vane/core/command/check/ParseCheckResult.java index 53c7486c7..35a8b81bb 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/check/ParseCheckResult.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/check/ParseCheckResult.java @@ -5,47 +5,47 @@ public class ParseCheckResult implements CheckResult { - private int depth; - private String argument_type; - private Object parsed; - private boolean include_param; - - public ParseCheckResult(int depth, String argument_type, Object parsed, boolean include_param) { - this.depth = depth; - this.argument_type = argument_type; - this.parsed = parsed; - this.include_param = include_param; - } - - public String argument_type() { - return argument_type; - } - - public Object parsed() { - return parsed; - } - - public boolean include_param() { - return include_param; - } - - @Override - public int depth() { - return depth; - } - - @Override - public boolean good() { - return true; - } - - @Override - public boolean apply(Command command, CommandSender sender) { - throw new RuntimeException("ParseCheckResult cannot be applied! This is a bug."); - } - - @Override - public CheckResult prepend(String argument_type, Object parsed_arg, boolean include) { - throw new RuntimeException("Cannot prepend to ParseCheckResult! This is a bug."); - } + private int depth; + private String argument_type; + private Object parsed; + private boolean include_param; + + public ParseCheckResult(int depth, String argument_type, Object parsed, boolean include_param) { + this.depth = depth; + this.argument_type = argument_type; + this.parsed = parsed; + this.include_param = include_param; + } + + public String argument_type() { + return argument_type; + } + + public Object parsed() { + return parsed; + } + + public boolean include_param() { + return include_param; + } + + @Override + public int depth() { + return depth; + } + + @Override + public boolean good() { + return true; + } + + @Override + public boolean apply(Command command, CommandSender sender) { + throw new RuntimeException("ParseCheckResult cannot be applied! This is a bug."); + } + + @Override + public CheckResult prepend(String argument_type, Object parsed_arg, boolean include) { + throw new RuntimeException("Cannot prepend to ParseCheckResult! This is a bug."); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/enums/WeatherValue.java b/vane-core/src/main/java/org/oddlama/vane/core/command/enums/WeatherValue.java index 7b062bed2..d2f8f3e3b 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/enums/WeatherValue.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/enums/WeatherValue.java @@ -21,4 +21,4 @@ public boolean storm() { public boolean thunder() { return is_thunder; } -} \ No newline at end of file +} diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/params/AnyParam.java b/vane-core/src/main/java/org/oddlama/vane/core/command/params/AnyParam.java index d358ef535..71a043df6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/params/AnyParam.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/params/AnyParam.java @@ -11,33 +11,33 @@ public class AnyParam extends BaseParam { - private String argument_type; - private Function1 from_string; + private String argument_type; + private Function1 from_string; - public AnyParam(Command command, String argument_type, Function1 from_string) { - super(command); - this.argument_type = argument_type; - this.from_string = from_string; - } + public AnyParam(Command command, String argument_type, Function1 from_string) { + super(command); + this.argument_type = argument_type; + this.from_string = from_string; + } - @Override - public CheckResult check_parse(CommandSender sender, String[] args, int offset) { - if (args.length <= offset) { - return new ErrorCheckResult(offset, "§6missing argument: §3" + argument_type + "§r"); - } - var parsed = parse(args[offset]); - if (parsed == null) { - return new ErrorCheckResult(offset, "§6invalid §3" + argument_type + "§6: §b" + args[offset] + "§r"); - } - return new ParseCheckResult(offset, argument_type, parsed, true); - } + @Override + public CheckResult check_parse(CommandSender sender, String[] args, int offset) { + if (args.length <= offset) { + return new ErrorCheckResult(offset, "§6missing argument: §3" + argument_type + "§r"); + } + var parsed = parse(args[offset]); + if (parsed == null) { + return new ErrorCheckResult(offset, "§6invalid §3" + argument_type + "§6: §b" + args[offset] + "§r"); + } + return new ParseCheckResult(offset, argument_type, parsed, true); + } - @Override - public List completions_for(CommandSender sender, String[] args, int offset) { - return Collections.emptyList(); - } + @Override + public List completions_for(CommandSender sender, String[] args, int offset) { + return Collections.emptyList(); + } - private T parse(String arg) { - return from_string.apply(arg); - } + private T parse(String arg) { + return from_string.apply(arg); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/params/BaseParam.java b/vane-core/src/main/java/org/oddlama/vane/core/command/params/BaseParam.java index c0d638ebb..fa461cd73 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/params/BaseParam.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/params/BaseParam.java @@ -7,20 +7,20 @@ public abstract class BaseParam implements Param { - private Command command; - private List params = new ArrayList<>(); + private Command command; + private List params = new ArrayList<>(); - public BaseParam(Command command) { - this.command = command; - } + public BaseParam(Command command) { + this.command = command; + } - @Override - public List get_params() { - return params; - } + @Override + public List get_params() { + return params; + } - @Override - public Command get_command() { - return command; - } + @Override + public Command get_command() { + return command; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/params/ChoiceParam.java b/vane-core/src/main/java/org/oddlama/vane/core/command/params/ChoiceParam.java index 104ba9c2b..951cba528 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/params/ChoiceParam.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/params/ChoiceParam.java @@ -13,63 +13,63 @@ public class ChoiceParam extends BaseParam { - private String argument_type; - private Collection choices; - private Function1 to_string; - private HashMap from_string = new HashMap<>(); - private boolean ignore_case = false; + private String argument_type; + private Collection choices; + private Function1 to_string; + private HashMap from_string = new HashMap<>(); + private boolean ignore_case = false; - public ChoiceParam( - Command command, - String argument_type, - Collection choices, - Function1 to_string - ) { - super(command); - this.argument_type = argument_type; - this.choices = choices; - this.to_string = to_string; - for (var c : choices) { - from_string.put(to_string.apply(c), c); - } - } + public ChoiceParam( + Command command, + String argument_type, + Collection choices, + Function1 to_string + ) { + super(command); + this.argument_type = argument_type; + this.choices = choices; + this.to_string = to_string; + for (var c : choices) { + from_string.put(to_string.apply(c), c); + } + } - /** Will ignore the case of the given argument when matching */ - public ChoiceParam ignore_case() { - this.ignore_case = true; - from_string.clear(); - for (var c : choices) { - from_string.put(to_string.apply(c), c); - } - return this; - } + /** Will ignore the case of the given argument when matching */ + public ChoiceParam ignore_case() { + this.ignore_case = true; + from_string.clear(); + for (var c : choices) { + from_string.put(to_string.apply(c), c); + } + return this; + } - @Override - public CheckResult check_parse(CommandSender sender, String[] args, int offset) { - if (args.length <= offset) { - return new ErrorCheckResult(offset, "§6missing argument: §3" + argument_type + "§r"); - } - var parsed = parse(args[offset]); - if (parsed == null) { - return new ErrorCheckResult(offset, "§6invalid §3" + argument_type + "§6: §b" + args[offset] + "§r"); - } - return new ParseCheckResult(offset, argument_type, parsed, true); - } + @Override + public CheckResult check_parse(CommandSender sender, String[] args, int offset) { + if (args.length <= offset) { + return new ErrorCheckResult(offset, "§6missing argument: §3" + argument_type + "§r"); + } + var parsed = parse(args[offset]); + if (parsed == null) { + return new ErrorCheckResult(offset, "§6invalid §3" + argument_type + "§6: §b" + args[offset] + "§r"); + } + return new ParseCheckResult(offset, argument_type, parsed, true); + } - @Override - public List completions_for(CommandSender sender, String[] args, int offset) { - return choices - .stream() - .map(choice -> to_string.apply(choice)) - .filter(str -> str.toLowerCase().contains(args[offset].toLowerCase())) - .collect(Collectors.toList()); - } + @Override + public List completions_for(CommandSender sender, String[] args, int offset) { + return choices + .stream() + .map(choice -> to_string.apply(choice)) + .filter(str -> str.toLowerCase().contains(args[offset].toLowerCase())) + .collect(Collectors.toList()); + } - private T parse(String arg) { - if (ignore_case) { - return from_string.get(arg.toLowerCase()); - } else { - return from_string.get(arg); - } - } + private T parse(String arg) { + if (ignore_case) { + return from_string.get(arg.toLowerCase()); + } else { + return from_string.get(arg); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/params/DynamicChoiceParam.java b/vane-core/src/main/java/org/oddlama/vane/core/command/params/DynamicChoiceParam.java index 7f26d3daa..8a9c239a8 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/params/DynamicChoiceParam.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/params/DynamicChoiceParam.java @@ -13,48 +13,48 @@ public class DynamicChoiceParam extends BaseParam { - private String argument_type; - private Function1> choices; - private Function2 to_string; - private Function2 from_string; + private String argument_type; + private Function1> choices; + private Function2 to_string; + private Function2 from_string; - public DynamicChoiceParam( - Command command, - String argument_type, - Function1> choices, - Function2 to_string, - Function2 from_string - ) { - super(command); - this.argument_type = argument_type; - this.choices = choices; - this.to_string = to_string; - this.from_string = from_string; - } + public DynamicChoiceParam( + Command command, + String argument_type, + Function1> choices, + Function2 to_string, + Function2 from_string + ) { + super(command); + this.argument_type = argument_type; + this.choices = choices; + this.to_string = to_string; + this.from_string = from_string; + } - @Override - public CheckResult check_parse(CommandSender sender, String[] args, int offset) { - if (args.length <= offset) { - return new ErrorCheckResult(offset, "§6missing argument: §3" + argument_type + "§r"); - } - var parsed = parse(sender, args[offset]); - if (parsed == null) { - return new ErrorCheckResult(offset, "§6invalid §3" + argument_type + "§6: §b" + args[offset] + "§r"); - } - return new ParseCheckResult(offset, argument_type, parsed, true); - } + @Override + public CheckResult check_parse(CommandSender sender, String[] args, int offset) { + if (args.length <= offset) { + return new ErrorCheckResult(offset, "§6missing argument: §3" + argument_type + "§r"); + } + var parsed = parse(sender, args[offset]); + if (parsed == null) { + return new ErrorCheckResult(offset, "§6invalid §3" + argument_type + "§6: §b" + args[offset] + "§r"); + } + return new ParseCheckResult(offset, argument_type, parsed, true); + } - @Override - public List completions_for(CommandSender sender, String[] args, int offset) { - return choices - .apply(sender) - .stream() - .map(choice -> to_string.apply(sender, choice)) - .filter(str -> str.toLowerCase().contains(args[offset].toLowerCase())) - .collect(Collectors.toList()); - } + @Override + public List completions_for(CommandSender sender, String[] args, int offset) { + return choices + .apply(sender) + .stream() + .map(choice -> to_string.apply(sender, choice)) + .filter(str -> str.toLowerCase().contains(args[offset].toLowerCase())) + .collect(Collectors.toList()); + } - private T parse(CommandSender sender, String arg) { - return from_string.apply(sender, arg); - } + private T parse(CommandSender sender, String arg) { + return from_string.apply(sender, arg); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/params/FixedParam.java b/vane-core/src/main/java/org/oddlama/vane/core/command/params/FixedParam.java index b8f496188..e0882e6de 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/params/FixedParam.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/params/FixedParam.java @@ -11,60 +11,60 @@ public class FixedParam extends BaseParam { - private T fixed_arg; - private String fixed_arg_str; - private boolean include_param = false; - private boolean ignore_case = false; + private T fixed_arg; + private String fixed_arg_str; + private boolean include_param = false; + private boolean ignore_case = false; - public FixedParam(Command command, T fixed_arg, Function1 to_string) { - super(command); - this.fixed_arg = fixed_arg; - this.fixed_arg_str = to_string.apply(fixed_arg); - } + public FixedParam(Command command, T fixed_arg, Function1 to_string) { + super(command); + this.fixed_arg = fixed_arg; + this.fixed_arg_str = to_string.apply(fixed_arg); + } - /** Will ignore the case of the given argument when matching */ - public FixedParam ignore_case() { - this.ignore_case = true; - return this; - } + /** Will ignore the case of the given argument when matching */ + public FixedParam ignore_case() { + this.ignore_case = true; + return this; + } - /** Will pass this fixed parameter as an argument to the executed function */ - public FixedParam include_param() { - this.include_param = true; - return this; - } + /** Will pass this fixed parameter as an argument to the executed function */ + public FixedParam include_param() { + this.include_param = true; + return this; + } - @Override - public CheckResult check_parse(CommandSender sender, String[] args, int offset) { - if (args.length <= offset) { - return new ErrorCheckResult(offset, "§6missing argument: §3" + fixed_arg_str + "§r"); - } - var parsed = parse(args[offset]); - if (parsed == null) { - return new ErrorCheckResult( - offset, - "§6invalid argument: expected §3" + fixed_arg_str + "§6 got §b" + args[offset] + "§r" - ); - } - return new ParseCheckResult(offset, fixed_arg_str, parsed, include_param); - } + @Override + public CheckResult check_parse(CommandSender sender, String[] args, int offset) { + if (args.length <= offset) { + return new ErrorCheckResult(offset, "§6missing argument: §3" + fixed_arg_str + "§r"); + } + var parsed = parse(args[offset]); + if (parsed == null) { + return new ErrorCheckResult( + offset, + "§6invalid argument: expected §3" + fixed_arg_str + "§6 got §b" + args[offset] + "§r" + ); + } + return new ParseCheckResult(offset, fixed_arg_str, parsed, include_param); + } - @Override - public List completions_for(CommandSender sender, String[] args, int offset) { - return Collections.singletonList(fixed_arg_str); - } + @Override + public List completions_for(CommandSender sender, String[] args, int offset) { + return Collections.singletonList(fixed_arg_str); + } - private T parse(String arg) { - if (ignore_case) { - if (arg.equalsIgnoreCase(fixed_arg_str)) { - return fixed_arg; - } - } else { - if (arg.equals(fixed_arg_str)) { - return fixed_arg; - } - } + private T parse(String arg) { + if (ignore_case) { + if (arg.equalsIgnoreCase(fixed_arg_str)) { + return fixed_arg; + } + } else { + if (arg.equals(fixed_arg_str)) { + return fixed_arg; + } + } - return null; - } + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/command/params/SentinelExecutorParam.java b/vane-core/src/main/java/org/oddlama/vane/core/command/params/SentinelExecutorParam.java index feccb1aa2..9d675fc23 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/command/params/SentinelExecutorParam.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/command/params/SentinelExecutorParam.java @@ -18,151 +18,153 @@ public class SentinelExecutorParam extends BaseParam implements Executor { - private T function; - private Function1 check_requirements; - private Function1 skip_argument_check; - - public SentinelExecutorParam(Command command, T function) { - this(command, function, x -> true); - } - - public SentinelExecutorParam(Command command, T function, Function1 check_requirements) { - this(command, function, check_requirements, i -> false); - } - - public SentinelExecutorParam( - Command command, - T function, - Function1 check_requirements, - Function1 skip_argument_check - ) { - super(command); - this.function = function; - this.check_requirements = check_requirements; - this.skip_argument_check = skip_argument_check; - } - - private boolean check_signature(final Method method, final List args) { - // Assert the same number of given and expected parameters - if (args.size() != method.getParameters().length) { - throw new RuntimeException( - "Invalid command functor " + - method.getDeclaringClass().getName() + - "::" + - method.getName() + - "!" + - "\nFunctor takes " + - method.getParameters().length + - " parameters, but " + - args.size() + - " were given." + - "\nRequired: " + - Arrays.stream(method.getParameters()).map(p -> p.getType().getName()).toList() + - "\nGiven: " + - args.stream().map(p -> p.getClass().getName()).toList() - ); - } - - // Assert assignable types - for (int i = 0; i < args.size(); ++i) { - if (skip_argument_check.apply(i)) { - continue; - } - var needs = method.getParameters()[i].getType(); - var got = args.get(i).getClass(); - if (!needs.isAssignableFrom(got)) { - throw new RuntimeException( - "Invalid command functor " + - method.getDeclaringClass().getName() + - "::" + - method.getName() + - "!" + - "\nArgument " + - (i + 1) + - " (" + - needs.getName() + - ") is not assignable from " + - got.getName() - ); - } - } - return true; - } - - @Override - public boolean is_executor() { - return true; - } - - @Override - public boolean execute(Command command, CommandSender sender, List parsed_args) { - // Replace command name argument (unused) with sender - parsed_args.set(0, sender); - - // Disable logger while reflecting on the lambda. - // FIXME? This is an ugly workaround to prevent Spigot from displaying - // a warning, that we load a class from a plugin we do not depend on, - // but this is absolutely intended, and erroneous behavior in any way. - var log = command.get_module().core.log; - var saved_filter = log.getFilter(); - log.setFilter(record -> false); - // Get method reflection - var gf = (GenericsFinder) function; - var method = gf.method(); - log.setFilter(saved_filter); - - // Check method signature against given argument types - check_signature(method, parsed_args); - - // Check external requirements on the sender - if (!check_requirements.apply(sender)) { - return false; - } - - // Execute functor - try { - var result = ((ErasedFunctor) function).invoke(parsed_args); - // Map null to "true" for consuming functions - return result == null || (boolean) result; - } catch (Exception e) { - throw new RuntimeException( - "Error while invoking functor " + method.getDeclaringClass().getName() + "::" + method.getName() + "!", - e - ); - } - } - - @Override - public void add_param(Param param) { - throw new RuntimeException("Cannot add element to sentinel executor! This is a bug."); - } - - @Override - public CheckResult check_parse(CommandSender sender, String[] args, int offset) { - return null; - } - - @Override - public List completions_for(CommandSender sender, String[] args, int offset) { - return Collections.emptyList(); - } - - @Override - public CheckResult check_accept(CommandSender sender, String[] args, int offset) { - if (args.length > offset) { - // Excess arguments are an error of the previous level, so we subtract one from the offset (depth) - // This will cause invalid arguments to be prioritized on optional arguments. - // For example /vane reload [module], with an invalid module name should show "invalid module" over - // excess arguments. - return new ErrorCheckResult( - offset - 1, - "§6excess arguments: {" + - Arrays.stream(args, offset, args.length).map(s -> "§4" + s + "§6").collect(Collectors.joining(", ")) + - "}§r" - ); - } else if (args.length < offset) { - throw new RuntimeException("Sentinel executor received missing arguments! This is a bug."); - } - return new ExecutorCheckResult(offset, this); - } + private T function; + private Function1 check_requirements; + private Function1 skip_argument_check; + + public SentinelExecutorParam(Command command, T function) { + this(command, function, x -> true); + } + + public SentinelExecutorParam(Command command, T function, Function1 check_requirements) { + this(command, function, check_requirements, i -> false); + } + + public SentinelExecutorParam( + Command command, + T function, + Function1 check_requirements, + Function1 skip_argument_check + ) { + super(command); + this.function = function; + this.check_requirements = check_requirements; + this.skip_argument_check = skip_argument_check; + } + + private boolean check_signature(final Method method, final List args) { + // Assert the same number of given and expected parameters + if (args.size() != method.getParameters().length) { + throw new RuntimeException( + "Invalid command functor " + + method.getDeclaringClass().getName() + + "::" + + method.getName() + + "!" + + "\nFunctor takes " + + method.getParameters().length + + " parameters, but " + + args.size() + + " were given." + + "\nRequired: " + + Arrays.stream(method.getParameters()).map(p -> p.getType().getName()).toList() + + "\nGiven: " + + args.stream().map(p -> p.getClass().getName()).toList() + ); + } + + // Assert assignable types + for (int i = 0; i < args.size(); ++i) { + if (skip_argument_check.apply(i)) { + continue; + } + var needs = method.getParameters()[i].getType(); + var got = args.get(i).getClass(); + if (!needs.isAssignableFrom(got)) { + throw new RuntimeException( + "Invalid command functor " + + method.getDeclaringClass().getName() + + "::" + + method.getName() + + "!" + + "\nArgument " + + (i + 1) + + " (" + + needs.getName() + + ") is not assignable from " + + got.getName() + ); + } + } + return true; + } + + @Override + public boolean is_executor() { + return true; + } + + @Override + public boolean execute(Command command, CommandSender sender, List parsed_args) { + // Replace command name argument (unused) with sender + parsed_args.set(0, sender); + + // Disable logger while reflecting on the lambda. + // FIXME? This is an ugly workaround to prevent Spigot from displaying + // a warning, that we load a class from a plugin we do not depend on, + // but this is absolutely intended, and erroneous behavior in any way. + var log = command.get_module().core.log; + var saved_filter = log.getFilter(); + log.setFilter(record -> false); + // Get method reflection + var gf = (GenericsFinder) function; + var method = gf.method(); + log.setFilter(saved_filter); + + // Check method signature against given argument types + check_signature(method, parsed_args); + + // Check external requirements on the sender + if (!check_requirements.apply(sender)) { + return false; + } + + // Execute functor + try { + var result = ((ErasedFunctor) function).invoke(parsed_args); + // Map null to "true" for consuming functions + return result == null || (boolean) result; + } catch (Exception e) { + throw new RuntimeException( + "Error while invoking functor " + method.getDeclaringClass().getName() + "::" + method.getName() + "!", + e + ); + } + } + + @Override + public void add_param(Param param) { + throw new RuntimeException("Cannot add element to sentinel executor! This is a bug."); + } + + @Override + public CheckResult check_parse(CommandSender sender, String[] args, int offset) { + return null; + } + + @Override + public List completions_for(CommandSender sender, String[] args, int offset) { + return Collections.emptyList(); + } + + @Override + public CheckResult check_accept(CommandSender sender, String[] args, int offset) { + if (args.length > offset) { + // Excess arguments are an error of the previous level, so we subtract one from the + // offset (depth) + // This will cause invalid arguments to be prioritized on optional arguments. + // For example /vane reload [module], with an invalid module name should show "invalid + // module" over + // excess arguments. + return new ErrorCheckResult( + offset - 1, + "§6excess arguments: {" + + Arrays.stream(args, offset, args.length).map(s -> "§4" + s + "§6").collect(Collectors.joining(", ")) + + "}§r" + ); + } else if (args.length < offset) { + throw new RuntimeException("Sentinel executor received missing arguments! This is a bug."); + } + return new ExecutorCheckResult(offset, this); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/commands/CustomItem.java b/vane-core/src/main/java/org/oddlama/vane/core/commands/CustomItem.java index 159e3053c..6d26afe38 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/commands/CustomItem.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/commands/CustomItem.java @@ -4,8 +4,9 @@ import static io.papermc.paper.command.brigadier.Commands.argument; import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.entity.Player; -import org.bukkit.permissions.PermissionDefault; import org.oddlama.vane.annotation.command.Name; import org.oddlama.vane.core.Core; import org.oddlama.vane.core.command.Command; @@ -13,37 +14,40 @@ import org.oddlama.vane.core.module.Context; import org.oddlama.vane.util.PlayerUtil; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; - -import io.papermc.paper.command.brigadier.CommandSourceStack; - @Name("customitem") public class CustomItem extends Command { - public CustomItem(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - // Help - return super.get_command_base().executes(stack -> { - print_help2(stack); - return SINGLE_SUCCESS; - }) - .then(help()) - // Give custom item - .then(literal("give") - .requires(stack -> stack.getSender() instanceof Player) - .then(argument("custom_item", CustomItemArgumentType.customItem(get_module())) - .executes(ctx -> { - org.oddlama.vane.core.item.api.CustomItem item = ctx.getArgument("custom_item", org.oddlama.vane.core.item.api.CustomItem.class); - give_custom_item((Player) ctx.getSource().getSender(), item); - return SINGLE_SUCCESS; - }))); - } - - private void give_custom_item(final Player player, final org.oddlama.vane.core.item.api.CustomItem custom_item) { - PlayerUtil.give_item(player, custom_item.newStack()); - } + public CustomItem(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + // Help + return super.get_command_base() + .executes(stack -> { + print_help2(stack); + return SINGLE_SUCCESS; + }) + .then(help()) + // Give custom item + .then( + literal("give") + .requires(stack -> stack.getSender() instanceof Player) + .then( + argument("custom_item", CustomItemArgumentType.customItem(get_module())).executes(ctx -> { + org.oddlama.vane.core.item.api.CustomItem item = ctx.getArgument( + "custom_item", + org.oddlama.vane.core.item.api.CustomItem.class + ); + give_custom_item((Player) ctx.getSource().getSender(), item); + return SINGLE_SUCCESS; + }) + ) + ); + } + + private void give_custom_item(final Player player, final org.oddlama.vane.core.item.api.CustomItem custom_item) { + PlayerUtil.give_item(player, custom_item.newStack()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/commands/Enchant.java b/vane-core/src/main/java/org/oddlama/vane/core/commands/Enchant.java index 199e3f1d7..107098b61 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/commands/Enchant.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/commands/Enchant.java @@ -2,8 +2,11 @@ import static com.mojang.brigadier.Command.SINGLE_SUCCESS; import static io.papermc.paper.command.brigadier.Commands.argument; -import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.enchantments.Enchantment; @@ -17,108 +20,103 @@ import org.oddlama.vane.core.lang.TranslatedMessage; import org.oddlama.vane.core.module.Context; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; - -import io.papermc.paper.command.brigadier.CommandSourceStack; - @Name("enchant") public class Enchant extends Command { - @LangMessage - private TranslatedMessage lang_level_too_low; - - @LangMessage - private TranslatedMessage lang_level_too_high; - - @LangMessage - private TranslatedMessage lang_invalid_enchantment; - - public Enchant(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - - return super.get_command_base() - .requires(ctx -> ctx.getSender() instanceof Player) - .then(help()) - .then( - argument("enchantment", EnchantmentFilterArgumentType.enchantmentFilter()) - .executes(ctx -> {enchant_current_item_level_1((Player) ctx.getSource().getSender(), enchantment(ctx)); return SINGLE_SUCCESS;}) - .then(argument("level", IntegerArgumentType.integer(1)) - .executes(ctx -> {enchant_current_item((Player) ctx.getSource().getSender(), enchantment(ctx), ctx.getArgument("level", Integer.class)); return SINGLE_SUCCESS;}) - ) - ); - } - - private Enchantment enchantment(CommandContext ctx){ - return ctx.getArgument("enchantment", Enchantment.class); - } - - private boolean filter_by_held_item(CommandSender sender, Enchantment e) { - if (!(sender instanceof Player)) { - return false; - } - - final var player = (Player) sender; - final var item_stack = player.getEquipment().getItemInMainHand(); - boolean is_book = item_stack.getType() == Material.BOOK || item_stack.getType() == Material.ENCHANTED_BOOK; - return is_book || e.canEnchantItem(item_stack); - } - - private void enchant_current_item_level_1(Player player, Enchantment enchantment) { - enchant_current_item(player, enchantment, 1); - } - - private void enchant_current_item(Player player, Enchantment enchantment, Integer level) { - if (level < enchantment.getStartLevel()) { - lang_level_too_low.send(player, "§b" + level, "§a" + enchantment.getStartLevel()); - return; - } else if (level > enchantment.getMaxLevel()) { - lang_level_too_high.send(player, "§b" + level, "§a" + enchantment.getMaxLevel()); - return; - } - - var item_stack = player.getEquipment().getItemInMainHand(); - if (item_stack.getType() == Material.AIR) { - lang_invalid_enchantment.send( - player, - "§b" + enchantment.getKey(), - "§a" + item_stack.getType().getKey() - ); - return; - } - - try { - // Convert a book if necessary - if (item_stack.getType() == Material.BOOK) { - // FIXME this technically yields wrong items when this was a tome, - // as just changing the base item is not equivalent to custom item conversion. - // The custom model data and item tag will still be those of a book. - // The fix is not straightforward without hardcoding tome identifiers, - // so for now we leave it as is. - item_stack = item_stack.withType(Material.ENCHANTED_BOOK); - /* fallthrough */ - } - - if (item_stack.getType() == Material.ENCHANTED_BOOK) { - final var meta = (EnchantmentStorageMeta) item_stack.getItemMeta(); - meta.addStoredEnchant(enchantment, level, false); - item_stack.setItemMeta(meta); - } else { - item_stack.addEnchantment(enchantment, level); - } - - get_module().enchantment_manager.update_enchanted_item(item_stack); - } catch (Exception e) { - lang_invalid_enchantment.send( - player, - "§b" + enchantment.getKey(), - "§a" + item_stack.getType().getKey() - ); - } - } + @LangMessage + private TranslatedMessage lang_level_too_low; + + @LangMessage + private TranslatedMessage lang_level_too_high; + + @LangMessage + private TranslatedMessage lang_invalid_enchantment; + + public Enchant(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .requires(ctx -> ctx.getSender() instanceof Player) + .then(help()) + .then( + argument("enchantment", EnchantmentFilterArgumentType.enchantmentFilter()) + .executes(ctx -> { + enchant_current_item_level_1((Player) ctx.getSource().getSender(), enchantment(ctx)); + return SINGLE_SUCCESS; + }) + .then( + argument("level", IntegerArgumentType.integer(1)).executes(ctx -> { + enchant_current_item( + (Player) ctx.getSource().getSender(), + enchantment(ctx), + ctx.getArgument("level", Integer.class) + ); + return SINGLE_SUCCESS; + }) + ) + ); + } + + private Enchantment enchantment(CommandContext ctx) { + return ctx.getArgument("enchantment", Enchantment.class); + } + + private boolean filter_by_held_item(CommandSender sender, Enchantment e) { + if (!(sender instanceof Player)) { + return false; + } + + final var player = (Player) sender; + final var item_stack = player.getEquipment().getItemInMainHand(); + boolean is_book = item_stack.getType() == Material.BOOK || item_stack.getType() == Material.ENCHANTED_BOOK; + return is_book || e.canEnchantItem(item_stack); + } + + private void enchant_current_item_level_1(Player player, Enchantment enchantment) { + enchant_current_item(player, enchantment, 1); + } + + private void enchant_current_item(Player player, Enchantment enchantment, Integer level) { + if (level < enchantment.getStartLevel()) { + lang_level_too_low.send(player, "§b" + level, "§a" + enchantment.getStartLevel()); + return; + } else if (level > enchantment.getMaxLevel()) { + lang_level_too_high.send(player, "§b" + level, "§a" + enchantment.getMaxLevel()); + return; + } + + var item_stack = player.getEquipment().getItemInMainHand(); + if (item_stack.getType() == Material.AIR) { + lang_invalid_enchantment.send(player, "§b" + enchantment.getKey(), "§a" + item_stack.getType().getKey()); + return; + } + + try { + // Convert a book if necessary + if (item_stack.getType() == Material.BOOK) { + // FIXME this technically yields wrong items when this was a tome, + // as just changing the base item is not equivalent to custom item conversion. + // The custom model data and item tag will still be those of a book. + // The fix is not straightforward without hardcoding tome identifiers, + // so for now we leave it as is. + item_stack = item_stack.withType(Material.ENCHANTED_BOOK); + /* fallthrough */ + } + + if (item_stack.getType() == Material.ENCHANTED_BOOK) { + final var meta = (EnchantmentStorageMeta) item_stack.getItemMeta(); + meta.addStoredEnchant(enchantment, level, false); + item_stack.setItemMeta(meta); + } else { + item_stack.addEnchantment(enchantment, level); + } + + get_module().enchantment_manager.update_enchanted_item(item_stack); + } catch (Exception e) { + lang_invalid_enchantment.send(player, "§b" + enchantment.getKey(), "§a" + item_stack.getType().getKey()); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/commands/Vane.java b/vane-core/src/main/java/org/oddlama/vane/core/commands/Vane.java index ddb044383..0ffa8a40b 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/commands/Vane.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/commands/Vane.java @@ -4,8 +4,9 @@ import static io.papermc.paper.command.brigadier.Commands.argument; import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; import java.util.Random; - import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.loot.LootContext; @@ -19,106 +20,141 @@ import org.oddlama.vane.core.module.Context; import org.oddlama.vane.core.module.Module; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; - -import io.papermc.paper.command.brigadier.CommandSourceStack; - @Name("vane") public class Vane extends Command { - @LangMessage - private TranslatedMessage lang_reload_success; - - @LangMessage - private TranslatedMessage lang_reload_fail; - - @LangMessage - private TranslatedMessage lang_resource_pack_generate_success; - - @LangMessage - private TranslatedMessage lang_resource_pack_generate_fail; - - public Vane(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .then(help()) - - .then(literal("reload").executes(ctx -> { reload_all(ctx.getSource().getSender()); return SINGLE_SUCCESS;}) - .then(argument("module", ModuleArgumentType.module(get_module())) - .executes(ctx -> { reload_module(ctx.getSource().getSender(), ctx.getArgument("module", Module.class)); return SINGLE_SUCCESS;}) - ) - ) - .then(literal("generate_resource_pack").executes(ctx -> { generate_resource_pack(ctx.getSource().getSender()); return SINGLE_SUCCESS; })) - .then(literal("test_do_not_use_if_you_are_not_a_dev").executes(ctx -> { test(ctx.getSource().getSender()); return SINGLE_SUCCESS; })); - } - - private void reload_module(CommandSender sender, Module module) { - if (module.reload_configuration()) { - lang_reload_success.send(sender, "§bvane-" + module.get_name()); - } else { - lang_reload_fail.send(sender, "§bvane-" + module.get_name()); - } - } - - private void reload_all(CommandSender sender) { - for (var m : get_module().core.get_modules()) { - reload_module(sender, m); - } - } - - private void generate_resource_pack(CommandSender sender) { - var file = get_module().generate_resource_pack(); - if (file != null) { - lang_resource_pack_generate_success.send(sender, file.getAbsolutePath()); - } else { - lang_resource_pack_generate_fail.send(sender); - } - if (sender instanceof Player) { - var dist = get_module().resource_pack_distributor; - dist.update_sha1(file); - dist.send_resource_pack((Player) sender); - } - } - - private void test_tome_generation() { - final var loot_table = LootTables.ABANDONED_MINESHAFT.getLootTable(); - final var inventory = get_module().getServer().createInventory(null, 3 * 9); - final var context = (new LootContext.Builder(get_module().getServer().getWorlds().get(0).getBlockAt(0,0,0).getLocation())).build(); - final var random = new Random(); - - int tomes = 0; - final var simulation_count = 10000; - final var gt_percentage = 0.2; // (0-2) (average 1) with 1/5 chance - final var tolerance = 0.7; - get_module().log.info("Testing ancient tome generation..."); - - for (int i = 0; i < simulation_count; ++i) { - inventory.clear(); - loot_table.fillInventory(inventory, random, context); - for (final var is : inventory.getStorageContents()) { - if (is != null && is.hasItemMeta()) { - final var meta = is.getItemMeta(); - if (meta.hasCustomModelData() && is.getItemMeta().getCustomModelData() == 0x770000) { - ++tomes; - } - } - } - } - - if (tomes == 0) { - get_module().log.severe("0 tomes were generated in " + simulation_count + " chests."); - } else if (tomes > gt_percentage * simulation_count * tolerance && tomes < gt_percentage * simulation_count / tolerance) { // 70% tolerance to lower bound - get_module().log.warning(tomes + " tomes were generated in " + simulation_count + " chests. This is " + (100.0 * ((double)tomes / simulation_count) / gt_percentage) + "% of the expected value."); - } else { - get_module().log.info(tomes + " tomes were generated in " + simulation_count + " chests. This is " + (100.0 * ((double)tomes / simulation_count) / gt_percentage) + "% of the expected value."); - } - } - - private void test(CommandSender sender) { - test_tome_generation(); - } + @LangMessage + private TranslatedMessage lang_reload_success; + + @LangMessage + private TranslatedMessage lang_reload_fail; + + @LangMessage + private TranslatedMessage lang_resource_pack_generate_success; + + @LangMessage + private TranslatedMessage lang_resource_pack_generate_fail; + + public Vane(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .then(help()) + .then( + literal("reload") + .executes(ctx -> { + reload_all(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + .then( + argument("module", ModuleArgumentType.module(get_module())).executes(ctx -> { + reload_module(ctx.getSource().getSender(), ctx.getArgument("module", Module.class)); + return SINGLE_SUCCESS; + }) + ) + ) + .then( + literal("generate_resource_pack").executes(ctx -> { + generate_resource_pack(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + ) + .then( + literal("test_do_not_use_if_you_are_not_a_dev").executes(ctx -> { + test(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + ); + } + + private void reload_module(CommandSender sender, Module module) { + if (module.reload_configuration()) { + lang_reload_success.send(sender, "§bvane-" + module.get_name()); + } else { + lang_reload_fail.send(sender, "§bvane-" + module.get_name()); + } + } + + private void reload_all(CommandSender sender) { + for (var m : get_module().core.get_modules()) { + reload_module(sender, m); + } + } + + private void generate_resource_pack(CommandSender sender) { + var file = get_module().generate_resource_pack(); + if (file != null) { + lang_resource_pack_generate_success.send(sender, file.getAbsolutePath()); + } else { + lang_resource_pack_generate_fail.send(sender); + } + if (sender instanceof Player) { + var dist = get_module().resource_pack_distributor; + dist.update_sha1(file); + dist.send_resource_pack((Player) sender); + } + } + + private void test_tome_generation() { + final var loot_table = LootTables.ABANDONED_MINESHAFT.getLootTable(); + final var inventory = get_module().getServer().createInventory(null, 3 * 9); + final var context = + (new LootContext.Builder( + get_module().getServer().getWorlds().get(0).getBlockAt(0, 0, 0).getLocation() + )).build(); + final var random = new Random(); + + int tomes = 0; + final var simulation_count = 10000; + final var gt_percentage = 0.2; // (0-2) (average 1) with 1/5 chance + final var tolerance = 0.7; + get_module().log.info("Testing ancient tome generation..."); + + for (int i = 0; i < simulation_count; ++i) { + inventory.clear(); + loot_table.fillInventory(inventory, random, context); + for (final var is : inventory.getStorageContents()) { + if (is != null && is.hasItemMeta()) { + final var meta = is.getItemMeta(); + if (meta.hasCustomModelData() && is.getItemMeta().getCustomModelData() == 0x770000) { + ++tomes; + } + } + } + } + + if (tomes == 0) { + get_module().log.severe("0 tomes were generated in " + simulation_count + " chests."); + } else if ( + tomes > gt_percentage * simulation_count * tolerance && + tomes < (gt_percentage * simulation_count) / tolerance + ) { // 70% tolerance to lower bound + get_module() + .log.warning( + tomes + + " tomes were generated in " + + simulation_count + + " chests. This is " + + ((100.0 * ((double) tomes / simulation_count)) / gt_percentage) + + "% of the expected value." + ); + } else { + get_module() + .log.info( + tomes + + " tomes were generated in " + + simulation_count + + " chests. This is " + + ((100.0 * ((double) tomes / simulation_count)) / gt_percentage) + + "% of the expected value." + ); + } + } + + private void test(CommandSender sender) { + test_tome_generation(); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigBooleanField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigBooleanField.java index 8e452f650..1c36a9aaf 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigBooleanField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigBooleanField.java @@ -8,61 +8,61 @@ public class ConfigBooleanField extends ConfigField { - private ConfigBoolean annotation; + private ConfigBoolean annotation; - public ConfigBooleanField(Object owner, Field field, Function map_name, ConfigBoolean annotation) { - super(owner, field, map_name, "boolean", annotation.desc()); - this.annotation = annotation; - } + public ConfigBooleanField(Object owner, Field field, Function map_name, ConfigBoolean annotation) { + super(owner, field, map_name, "boolean", annotation.desc()); + this.annotation = annotation; + } - @Override - public Boolean def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return annotation.def(); - } - } + @Override + public Boolean def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return annotation.def(); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_default_value(builder, indent, def()); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition(builder, indent, def); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_default_value(builder, indent, def()); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition(builder, indent, def); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isBoolean(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected boolean"); - } - } + if (!yaml.isBoolean(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected boolean"); + } + } - public boolean load_from_yaml(YamlConfiguration yaml) { - return yaml.getBoolean(yaml_path()); - } + public boolean load_from_yaml(YamlConfiguration yaml) { + return yaml.getBoolean(yaml_path()); + } - public void load(YamlConfiguration yaml) { - try { - field.setBoolean(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.setBoolean(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictField.java index f00f08c01..d2c1ee03c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictField.java @@ -7,213 +7,266 @@ import java.util.List; import java.util.Map; import java.util.function.Function; - import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import org.oddlama.vane.annotation.config.ConfigDict; import org.oddlama.vane.core.YamlLoadException; public class ConfigDictField extends ConfigField { - private class EmptyDict implements ConfigDictSerializable { - @Override - public Map to_dict() { - return new HashMap<>(); - } - - @Override - public void from_dict(final Map dict) { - // no-op - } - } - - public ConfigDict annotation; - - public ConfigDictField( - final Object owner, - final Field field, - final Function map_name, - final ConfigDict annotation - ) { - super(owner, field, map_name, "dict", annotation.desc()); - this.annotation = annotation; - } - - @Override - public ConfigDictSerializable def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return new EmptyDict(); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @SuppressWarnings("unchecked") - private void append_list(final StringBuilder builder, final String indent, final String list_key, final List list) { - builder.append(indent); - builder.append(list_key); - if (list.isEmpty()) { - builder.append(": []\n"); - } else { - builder.append(":\n"); - list.forEach(entry -> { - if (entry instanceof String) { - builder.append(indent); - builder.append(" - "); - builder.append("\"" + escape_yaml(entry.toString()) + "\""); - builder.append("\n"); - } else if ( - entry instanceof Integer || - entry instanceof Long || - entry instanceof Float || - entry instanceof Double || - entry instanceof Boolean - ) { - builder.append(indent); - builder.append(" - "); - builder.append(entry); - builder.append("\n"); - } else if (entry instanceof Map) { - append_dict(builder, indent + " ", null, (Map)entry, true); - } else { - throw new RuntimeException("Invalid value '" + entry + "' of type " + entry.getClass() + " in mapping of ConfigDictSerializable"); - } - }); - } - } - - @SuppressWarnings("unchecked") - private void append_dict(final StringBuilder builder, final String indent, final String dict_key, final Map dict, final boolean is_list_entry) { - builder.append(indent); - if (is_list_entry) { - builder.append("-"); - } else { - builder.append(dict_key); - builder.append(":"); - } - if (dict.isEmpty()) { - builder.append(" {}\n"); - } else { - builder.append("\n"); - dict.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { - if (entry.getValue() instanceof String) { - builder.append(indent + " "); - builder.append(entry.getKey()); - builder.append(": "); - builder.append("\"" + escape_yaml(entry.getValue().toString()) + "\""); - builder.append("\n"); - } else if ( - entry.getValue() instanceof Integer || - entry.getValue() instanceof Long || - entry.getValue() instanceof Float || - entry.getValue() instanceof Double || - entry.getValue() instanceof Boolean - ) { - builder.append(indent + " "); - builder.append(entry.getKey()); - builder.append(": "); - builder.append(entry.getValue().toString()); - builder.append("\n"); - } else if (entry.getValue() instanceof Map) { - append_dict(builder, indent + " ", entry.getKey(), (Map)entry.getValue(), false); - } else if (entry.getValue() instanceof List) { - append_list(builder, indent + " ", entry.getKey(), (List)entry.getValue()); - } else { - throw new RuntimeException("Invalid value '" + entry.getValue() + "' of type " + entry.getValue().getClass() + " in mapping of ConfigDictSerializable"); - } - }); - } - } - - private void append_dict(final StringBuilder builder, final String indent, final boolean default_definition, final ConfigDictSerializable ser) { - if (default_definition) { - append_dict(builder, indent + "# ", "Default", ser.to_dict(), false); - } else { - append_dict(builder, indent, basename(), ser.to_dict(), false); - } - } - - @Override - public void generate_yaml(final StringBuilder builder, final String indent, final YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_dict(builder, indent, true, def()); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_dict(builder, indent, false, def); - } - - @Override - public void check_loadable(final YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isConfigurationSection(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected configuration section"); - } - } - - public ArrayList load_list_from_yaml(final List raw_list) { - final var list = new ArrayList(); - for (var e : raw_list) { - if (e instanceof ConfigurationSection section) { - list.add(load_dict_from_yaml(section)); - } else { - list.add(e); - } - } - return list; - } - - public HashMap load_dict_from_yaml(final ConfigurationSection section) { - final var dict = new HashMap(); - for (var subkey : section.getKeys(false)) { - if (section.isConfigurationSection(subkey)) { - dict.put(subkey, load_dict_from_yaml(section.getConfigurationSection(subkey))); - } else if (section.isList(subkey)) { - dict.put(subkey, load_list_from_yaml(section.getList(subkey))); - } else if (section.isString(subkey)) { - dict.put(subkey, section.getString(subkey)); - } else if (section.isInt(subkey)) { - dict.put(subkey, section.getInt(subkey)); - } else if (section.isDouble(subkey)) { - dict.put(subkey, section.getDouble(subkey)); - } else if (section.isBoolean(subkey)) { - dict.put(subkey, section.getBoolean(subkey)); - } else if (section.isLong(subkey)) { - dict.put(subkey, section.getLong(subkey)); - } else { - throw new IllegalStateException("Cannot load dict entry '" + yaml_path() + "." + subkey + "': unknown type"); - } - } - return dict; - } - - public ConfigDictSerializable load_from_yaml(final YamlConfiguration yaml) { - try { - final var dict = ((ConfigDictSerializable)annotation.cls().getDeclaredConstructor().newInstance()); - dict.from_dict(load_dict_from_yaml(yaml.getConfigurationSection(yaml_path()))); - return dict; - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException - | NoSuchMethodException | SecurityException e) { - throw new RuntimeException("Could not instanciate storage class for ConfigDict: " + annotation.cls(), e); - } - } - - public void load(final YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (final IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + + private class EmptyDict implements ConfigDictSerializable { + + @Override + public Map to_dict() { + return new HashMap<>(); + } + + @Override + public void from_dict(final Map dict) { + // no-op + } + } + + public ConfigDict annotation; + + public ConfigDictField( + final Object owner, + final Field field, + final Function map_name, + final ConfigDict annotation + ) { + super(owner, field, map_name, "dict", annotation.desc()); + this.annotation = annotation; + } + + @Override + public ConfigDictSerializable def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return new EmptyDict(); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @SuppressWarnings("unchecked") + private void append_list( + final StringBuilder builder, + final String indent, + final String list_key, + final List list + ) { + builder.append(indent); + builder.append(list_key); + if (list.isEmpty()) { + builder.append(": []\n"); + } else { + builder.append(":\n"); + list.forEach(entry -> { + if (entry instanceof String) { + builder.append(indent); + builder.append(" - "); + builder.append("\"" + escape_yaml(entry.toString()) + "\""); + builder.append("\n"); + } else if ( + entry instanceof Integer || + entry instanceof Long || + entry instanceof Float || + entry instanceof Double || + entry instanceof Boolean + ) { + builder.append(indent); + builder.append(" - "); + builder.append(entry); + builder.append("\n"); + } else if (entry instanceof Map) { + append_dict(builder, indent + " ", null, (Map) entry, true); + } else { + throw new RuntimeException( + "Invalid value '" + + entry + + "' of type " + + entry.getClass() + + " in mapping of ConfigDictSerializable" + ); + } + }); + } + } + + @SuppressWarnings("unchecked") + private void append_dict( + final StringBuilder builder, + final String indent, + final String dict_key, + final Map dict, + final boolean is_list_entry + ) { + builder.append(indent); + if (is_list_entry) { + builder.append("-"); + } else { + builder.append(dict_key); + builder.append(":"); + } + if (dict.isEmpty()) { + builder.append(" {}\n"); + } else { + builder.append("\n"); + dict + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> { + if (entry.getValue() instanceof String) { + builder.append(indent + " "); + builder.append(entry.getKey()); + builder.append(": "); + builder.append("\"" + escape_yaml(entry.getValue().toString()) + "\""); + builder.append("\n"); + } else if ( + entry.getValue() instanceof Integer || + entry.getValue() instanceof Long || + entry.getValue() instanceof Float || + entry.getValue() instanceof Double || + entry.getValue() instanceof Boolean + ) { + builder.append(indent + " "); + builder.append(entry.getKey()); + builder.append(": "); + builder.append(entry.getValue().toString()); + builder.append("\n"); + } else if (entry.getValue() instanceof Map) { + append_dict( + builder, + indent + " ", + entry.getKey(), + (Map) entry.getValue(), + false + ); + } else if (entry.getValue() instanceof List) { + append_list(builder, indent + " ", entry.getKey(), (List) entry.getValue()); + } else { + throw new RuntimeException( + "Invalid value '" + + entry.getValue() + + "' of type " + + entry.getValue().getClass() + + " in mapping of ConfigDictSerializable" + ); + } + }); + } + } + + private void append_dict( + final StringBuilder builder, + final String indent, + final boolean default_definition, + final ConfigDictSerializable ser + ) { + if (default_definition) { + append_dict(builder, indent + "# ", "Default", ser.to_dict(), false); + } else { + append_dict(builder, indent, basename(), ser.to_dict(), false); + } + } + + @Override + public void generate_yaml( + final StringBuilder builder, + final String indent, + final YamlConfiguration existing_compatible_config + ) { + append_description(builder, indent); + append_dict(builder, indent, true, def()); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_dict(builder, indent, false, def); + } + + @Override + public void check_loadable(final YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isConfigurationSection(yaml_path())) { + throw new YamlLoadException( + "Invalid type for yaml path '" + yaml_path() + "', expected configuration section" + ); + } + } + + public ArrayList load_list_from_yaml(final List raw_list) { + final var list = new ArrayList(); + for (var e : raw_list) { + if (e instanceof ConfigurationSection section) { + list.add(load_dict_from_yaml(section)); + } else { + list.add(e); + } + } + return list; + } + + public HashMap load_dict_from_yaml(final ConfigurationSection section) { + final var dict = new HashMap(); + for (var subkey : section.getKeys(false)) { + if (section.isConfigurationSection(subkey)) { + dict.put(subkey, load_dict_from_yaml(section.getConfigurationSection(subkey))); + } else if (section.isList(subkey)) { + dict.put(subkey, load_list_from_yaml(section.getList(subkey))); + } else if (section.isString(subkey)) { + dict.put(subkey, section.getString(subkey)); + } else if (section.isInt(subkey)) { + dict.put(subkey, section.getInt(subkey)); + } else if (section.isDouble(subkey)) { + dict.put(subkey, section.getDouble(subkey)); + } else if (section.isBoolean(subkey)) { + dict.put(subkey, section.getBoolean(subkey)); + } else if (section.isLong(subkey)) { + dict.put(subkey, section.getLong(subkey)); + } else { + throw new IllegalStateException( + "Cannot load dict entry '" + yaml_path() + "." + subkey + "': unknown type" + ); + } + } + return dict; + } + + public ConfigDictSerializable load_from_yaml(final YamlConfiguration yaml) { + try { + final var dict = ((ConfigDictSerializable) annotation.cls().getDeclaredConstructor().newInstance()); + dict.from_dict(load_dict_from_yaml(yaml.getConfigurationSection(yaml_path()))); + return dict; + } catch ( + InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | NoSuchMethodException + | SecurityException e + ) { + throw new RuntimeException("Could not instanciate storage class for ConfigDict: " + annotation.cls(), e); + } + } + + public void load(final YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (final IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictSerializable.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictSerializable.java index 5035eae94..96869f469 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictSerializable.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDictSerializable.java @@ -3,6 +3,7 @@ import java.util.Map; public interface ConfigDictSerializable { - public Map to_dict(); - public void from_dict(Map dict); + public Map to_dict(); + + public void from_dict(Map dict); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleField.java index 1dd901f7f..8d3c08bb4 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleField.java @@ -8,74 +8,74 @@ public class ConfigDoubleField extends ConfigField { - public ConfigDouble annotation; + public ConfigDouble annotation; - public ConfigDoubleField(Object owner, Field field, Function map_name, ConfigDouble annotation) { - super(owner, field, map_name, "double", annotation.desc()); - this.annotation = annotation; - } + public ConfigDoubleField(Object owner, Field field, Function map_name, ConfigDouble annotation) { + super(owner, field, map_name, "double", annotation.desc()); + this.annotation = annotation; + } - @Override - public Double def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return annotation.def(); - } - } + @Override + public Double def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return annotation.def(); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_value_range(builder, indent, annotation.min(), annotation.max(), Double.NaN, Double.NaN); - append_default_value(builder, indent, def()); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition(builder, indent, def); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_value_range(builder, indent, annotation.min(), annotation.max(), Double.NaN, Double.NaN); + append_default_value(builder, indent, def()); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition(builder, indent, def); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isDouble(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected double"); - } + if (!yaml.isDouble(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected double"); + } - var val = yaml.getDouble(yaml_path()); - if (!Double.isNaN(annotation.min()) && val < annotation.min()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() - ); - } - if (!Double.isNaN(annotation.max()) && val > annotation.max()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() - ); - } - } + var val = yaml.getDouble(yaml_path()); + if (!Double.isNaN(annotation.min()) && val < annotation.min()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() + ); + } + if (!Double.isNaN(annotation.max()) && val > annotation.max()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() + ); + } + } - public double load_from_yaml(YamlConfiguration yaml) { - return yaml.getDouble(yaml_path()); - } + public double load_from_yaml(YamlConfiguration yaml) { + return yaml.getDouble(yaml_path()); + } - public void load(YamlConfiguration yaml) { - try { - field.setDouble(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.setDouble(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleListField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleListField.java index c4734d623..d2ada8656 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleListField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigDoubleListField.java @@ -12,102 +12,102 @@ public class ConfigDoubleListField extends ConfigField> { - public ConfigDoubleList annotation; - - public ConfigDoubleListField( - Object owner, - Field field, - Function map_name, - ConfigDoubleList annotation - ) { - super(owner, field, map_name, "double list", annotation.desc()); - this.annotation = annotation; - } - - private void append_double_list_definition(StringBuilder builder, String indent, String prefix, List def) { - append_list_definition(builder, indent, prefix, def, (b, d) -> b.append(d)); - } - - @Override - public List def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return Arrays.asList(ArrayUtils.toObject(annotation.def())); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_value_range(builder, indent, annotation.min(), annotation.max(), Double.NaN, Double.NaN); - - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_double_list_definition(builder, indent, "# ", def()); - - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_double_list_definition(builder, indent, "", def); - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isList(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); - } - - for (var obj : yaml.getList(yaml_path())) { - if (!(obj instanceof Number)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected double"); - } - - var val = yaml.getDouble(yaml_path()); - if (!Double.isNaN(annotation.min()) && val < annotation.min()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() - ); - } - if (!Double.isNaN(annotation.max()) && val > annotation.max()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() - ); - } - } - } - - public List load_from_yaml(YamlConfiguration yaml) { - final var list = new ArrayList(); - for (var obj : yaml.getList(yaml_path())) { - list.add(((Number) obj).doubleValue()); - } - return list; - } - - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public ConfigDoubleList annotation; + + public ConfigDoubleListField( + Object owner, + Field field, + Function map_name, + ConfigDoubleList annotation + ) { + super(owner, field, map_name, "double list", annotation.desc()); + this.annotation = annotation; + } + + private void append_double_list_definition(StringBuilder builder, String indent, String prefix, List def) { + append_list_definition(builder, indent, prefix, def, (b, d) -> b.append(d)); + } + + @Override + public List def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return Arrays.asList(ArrayUtils.toObject(annotation.def())); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_value_range(builder, indent, annotation.min(), annotation.max(), Double.NaN, Double.NaN); + + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_double_list_definition(builder, indent, "# ", def()); + + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_double_list_definition(builder, indent, "", def); + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isList(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); + } + + for (var obj : yaml.getList(yaml_path())) { + if (!(obj instanceof Number)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected double"); + } + + var val = yaml.getDouble(yaml_path()); + if (!Double.isNaN(annotation.min()) && val < annotation.min()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() + ); + } + if (!Double.isNaN(annotation.max()) && val > annotation.max()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() + ); + } + } + } + + public List load_from_yaml(YamlConfiguration yaml) { + final var list = new ArrayList(); + for (var obj : yaml.getList(yaml_path())) { + list.add(((Number) obj).doubleValue()); + } + return list; + } + + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigExtendedMaterialField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigExtendedMaterialField.java index cd271bf0b..ff73cfc3f 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigExtendedMaterialField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigExtendedMaterialField.java @@ -11,104 +11,104 @@ public class ConfigExtendedMaterialField extends ConfigField { - public ConfigExtendedMaterial annotation; + public ConfigExtendedMaterial annotation; - public ConfigExtendedMaterialField( - Object owner, - Field field, - Function map_name, - ConfigExtendedMaterial annotation - ) { - super(owner, field, map_name, "extended material", annotation.desc()); - this.annotation = annotation; - } + public ConfigExtendedMaterialField( + Object owner, + Field field, + Function map_name, + ConfigExtendedMaterial annotation + ) { + super(owner, field, map_name, "extended material", annotation.desc()); + this.annotation = annotation; + } - @Override - public ExtendedMaterial def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - final var split = annotation.def().split(":"); - if (split.length != 2) { - throw new RuntimeException( - "Invalid default extended material entry for '" + - yaml_path() + - "': '" + - annotation.def() + - "' is not a valid namespaced key" - ); - } - return ExtendedMaterial.from(namespaced_key(split[0], split[1])); - } - } + @Override + public ExtendedMaterial def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + final var split = annotation.def().split(":"); + if (split.length != 2) { + throw new RuntimeException( + "Invalid default extended material entry for '" + + yaml_path() + + "': '" + + annotation.def() + + "' is not a valid namespaced key" + ); + } + return ExtendedMaterial.from(namespaced_key(split[0], split[1])); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_default_value( - builder, - indent, - "\"" + escape_yaml(def().key().getNamespace()) + ":" + escape_yaml(def().key().getKey()) + "\"" - ); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition( - builder, - indent, - "\"" + escape_yaml(def.key().getNamespace()) + ":" + escape_yaml(def.key().getKey()) + "\"" - ); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_default_value( + builder, + indent, + "\"" + escape_yaml(def().key().getNamespace()) + ":" + escape_yaml(def().key().getKey()) + "\"" + ); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition( + builder, + indent, + "\"" + escape_yaml(def.key().getNamespace()) + ":" + escape_yaml(def.key().getKey()) + "\"" + ); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isString(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); - } + if (!yaml.isString(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); + } - final var str = yaml.getString(yaml_path()); - final var split = str.split(":"); - if (split.length != 2) { - throw new YamlLoadException( - "Invalid extended material entry in list '" + - yaml_path() + - "': '" + - str + - "' is not a valid namespaced key" - ); - } + final var str = yaml.getString(yaml_path()); + final var split = str.split(":"); + if (split.length != 2) { + throw new YamlLoadException( + "Invalid extended material entry in list '" + + yaml_path() + + "': '" + + str + + "' is not a valid namespaced key" + ); + } - final var mat = ExtendedMaterial.from(namespaced_key(split[0], split[1])); - if (mat == null) { - throw new YamlLoadException( - "Invalid extended material entry in list '" + yaml_path() + "': '" + str + "' does not exist" - ); - } - } + final var mat = ExtendedMaterial.from(namespaced_key(split[0], split[1])); + if (mat == null) { + throw new YamlLoadException( + "Invalid extended material entry in list '" + yaml_path() + "': '" + str + "' does not exist" + ); + } + } - public ExtendedMaterial load_from_yaml(YamlConfiguration yaml) { - final var split = yaml.getString(yaml_path()).split(":"); - return ExtendedMaterial.from(namespaced_key(split[0], split[1])); - } + public ExtendedMaterial load_from_yaml(YamlConfiguration yaml) { + final var split = yaml.getString(yaml_path()).split(":"); + return ExtendedMaterial.from(namespaced_key(split[0], split[1])); + } - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigField.java index dd72c2299..b93bcf0e3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigField.java @@ -5,7 +5,6 @@ import java.util.Collection; import java.util.function.Function; import java.util.function.Supplier; - import org.apache.commons.lang.WordUtils; import org.bstats.bukkit.Metrics; import org.bstats.charts.SimplePie; @@ -15,285 +14,285 @@ public abstract class ConfigField implements Comparable> { - protected Object owner; - protected Field field; - protected String path; - protected String type_name; - protected int sort_priority = 0; - - private String[] yaml_path_components; - private String yaml_group_path; - private String basename; - private Supplier description; - - public ConfigField( - Object owner, - Field field, - Function map_name, - String type_name, - String description - ) { - this.owner = owner; - this.field = field; - this.path = map_name.apply(field.getName().substring("config_".length())); - this.yaml_path_components = path.split("\\."); - - var last_dot = path.lastIndexOf("."); - this.yaml_group_path = last_dot == -1 ? "" : path.substring(0, last_dot); - - this.basename = yaml_path_components[yaml_path_components.length - 1]; - this.type_name = type_name; - - // lang, enabled, metrics_enabled should be at the top - switch (this.path) { - case "lang" -> this.sort_priority = -10; - case "enabled" -> this.sort_priority = -9; - case "metrics_enabled" -> this.sort_priority = -8; - } - - field.setAccessible(true); - - // Dynamic description - this.description = () -> { - try { - return (String) owner.getClass().getMethod(field.getName() + "_desc").invoke(owner); - } catch (NoSuchMethodException e) { - // Ignore, field wasn't overridden - return description; - } catch (InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException( - "Could not call " + - owner.getClass().getName() + - "." + - field.getName() + - "_desc() to override description value", - e - ); - } - }; - } - - @SuppressWarnings("unchecked") - protected T overridden_def() { - try { - return (T) owner.getClass().getMethod(field.getName() + "_def").invoke(owner); - } catch (NoSuchMethodException e) { - // Ignore, field wasn't overridden - return null; - } catch (InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException( - "Could not call " + - owner.getClass().getName() + - "." + - field.getName() + - "_def() to override default value", - e - ); - } - } - - protected Boolean overridden_metrics() { - try { - return (Boolean)owner.getClass().getMethod(field.getName() + "_metrics").invoke(owner); - } catch (NoSuchMethodException e) { - // Ignore, field wasn't overridden - return null; - } catch (InvocationTargetException | IllegalAccessException e) { - throw new RuntimeException( - "Could not call " + - owner.getClass().getName() + - "." + - field.getName() + - "_metrics() to override metrics status", - e - ); - } - } - - protected String escape_yaml(String s) { - return s.replace("\\", "\\\\").replace("\"", "\\\""); - } - - public String get_yaml_group_path() { - return path; - } - - public String yaml_path() { - return path; - } - - public String yaml_group_path() { - return yaml_group_path; - } - - public String basename() { - return basename; - } - - private String modify_yaml_path_for_sorting(String path) { - // "enable" fields should always be at the top, and therefore - // get treated without the suffix. - if (path.endsWith(".enabled")) { - return path.substring(0, path.lastIndexOf(".enabled")); - } - return path; - } - - @Override - public int compareTo(ConfigField other) { - if (sort_priority != other.sort_priority) { - return sort_priority - other.sort_priority; - } else { - for (int i = 0; i < Math.min(yaml_path_components.length, other.yaml_path_components.length) - 1; ++i) { - var c = yaml_path_components[i].compareTo(other.yaml_path_components[i]); - if (c != 0) { - return c; - } - } - return modify_yaml_path_for_sorting(yaml_path()).compareTo(modify_yaml_path_for_sorting(other.yaml_path())); - } - } - - protected void append_description(StringBuilder builder, String indent) { - final var description_wrapped = - indent + - "# " + - WordUtils.wrap(description.get(), Math.max(60, 80 - indent.length()), "\n" + indent + "# ", false); - builder.append(description_wrapped); - builder.append("\n"); - } - - protected void append_list_definition( - StringBuilder builder, - String indent, - String prefix, - Collection list, - Consumer2 append - ) { - list - .stream() - .forEach(i -> { - builder.append(indent); - builder.append(prefix); - builder.append(" - "); - append.apply(builder, i); - builder.append("\n"); - }); - } - - protected void append_value_range( - StringBuilder builder, - String indent, - U min, - U max, - U invalid_min, - U invalid_max - ) { - builder.append(indent); - builder.append("# Valid values: "); - if (!min.equals(invalid_min)) { - if (!max.equals(invalid_max)) { - builder.append("["); - builder.append(min); - builder.append(","); - builder.append(max); - builder.append("]"); - } else { - builder.append("["); - builder.append(min); - builder.append(",)"); - } - } else { - if (!max.equals(invalid_max)) { - builder.append("(,"); - builder.append(max); - builder.append("]"); - } else { - builder.append("Any " + type_name); - } - } - builder.append("\n"); - } - - protected void append_default_value(StringBuilder builder, String indent, Object def) { - builder.append(indent); - builder.append("# Default: "); - builder.append(def); - builder.append("\n"); - } - - protected void append_field_definition(StringBuilder builder, String indent, Object def) { - builder.append(indent); - builder.append(basename); - builder.append(": "); - builder.append(def); - builder.append("\n"); - } - - protected void check_yaml_path(YamlConfiguration yaml) throws YamlLoadException { - if (!yaml.contains(path, true)) { - throw new YamlLoadException("yaml is missing entry with path '" + path + "'"); - } - } - - public abstract T def(); - - // Disabled by default, fields must explicitly support this! - public boolean metrics() { - return false; - } - - public abstract void generate_yaml( - StringBuilder builder, - String indent, - YamlConfiguration existing_compatible_config - ); - - public abstract void check_loadable(YamlConfiguration yaml) throws YamlLoadException; - - public abstract void load(YamlConfiguration yaml); - - @SuppressWarnings("unchecked") - public T get() { - try { - return (T) field.get(owner); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } - - public void register_metrics(Metrics metrics) { - if (!metrics()) return; - metrics.addCustomChart(new SimplePie(yaml_path(), () -> get().toString())); - } - - public String[] components() { - return yaml_path_components; - } - - public int group_count() { - return yaml_path_components.length - 1; - } - - public static boolean same_group(ConfigField a, ConfigField b) { - if (a.yaml_path_components.length != b.yaml_path_components.length) { - return false; - } - for (int i = 0; i < a.yaml_path_components.length - 1; ++i) { - if (!a.yaml_path_components[i].equals(b.yaml_path_components[i])) { - return false; - } - } - return true; - } - - public static int common_group_count(ConfigField a, ConfigField b) { - int i; - for (i = 0; i < Math.min(a.yaml_path_components.length, b.yaml_path_components.length) - 1; ++i) { - if (!a.yaml_path_components[i].equals(b.yaml_path_components[i])) { - return i; - } - } - return i; - } + protected Object owner; + protected Field field; + protected String path; + protected String type_name; + protected int sort_priority = 0; + + private String[] yaml_path_components; + private String yaml_group_path; + private String basename; + private Supplier description; + + public ConfigField( + Object owner, + Field field, + Function map_name, + String type_name, + String description + ) { + this.owner = owner; + this.field = field; + this.path = map_name.apply(field.getName().substring("config_".length())); + this.yaml_path_components = path.split("\\."); + + var last_dot = path.lastIndexOf("."); + this.yaml_group_path = last_dot == -1 ? "" : path.substring(0, last_dot); + + this.basename = yaml_path_components[yaml_path_components.length - 1]; + this.type_name = type_name; + + // lang, enabled, metrics_enabled should be at the top + switch (this.path) { + case "lang" -> this.sort_priority = -10; + case "enabled" -> this.sort_priority = -9; + case "metrics_enabled" -> this.sort_priority = -8; + } + + field.setAccessible(true); + + // Dynamic description + this.description = () -> { + try { + return (String) owner.getClass().getMethod(field.getName() + "_desc").invoke(owner); + } catch (NoSuchMethodException e) { + // Ignore, field wasn't overridden + return description; + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException( + "Could not call " + + owner.getClass().getName() + + "." + + field.getName() + + "_desc() to override description value", + e + ); + } + }; + } + + @SuppressWarnings("unchecked") + protected T overridden_def() { + try { + return (T) owner.getClass().getMethod(field.getName() + "_def").invoke(owner); + } catch (NoSuchMethodException e) { + // Ignore, field wasn't overridden + return null; + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException( + "Could not call " + + owner.getClass().getName() + + "." + + field.getName() + + "_def() to override default value", + e + ); + } + } + + protected Boolean overridden_metrics() { + try { + return (Boolean) owner.getClass().getMethod(field.getName() + "_metrics").invoke(owner); + } catch (NoSuchMethodException e) { + // Ignore, field wasn't overridden + return null; + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException( + "Could not call " + + owner.getClass().getName() + + "." + + field.getName() + + "_metrics() to override metrics status", + e + ); + } + } + + protected String escape_yaml(String s) { + return s.replace("\\", "\\\\").replace("\"", "\\\""); + } + + public String get_yaml_group_path() { + return path; + } + + public String yaml_path() { + return path; + } + + public String yaml_group_path() { + return yaml_group_path; + } + + public String basename() { + return basename; + } + + private String modify_yaml_path_for_sorting(String path) { + // "enable" fields should always be at the top, and therefore + // get treated without the suffix. + if (path.endsWith(".enabled")) { + return path.substring(0, path.lastIndexOf(".enabled")); + } + return path; + } + + @Override + public int compareTo(ConfigField other) { + if (sort_priority != other.sort_priority) { + return sort_priority - other.sort_priority; + } else { + for (int i = 0; i < Math.min(yaml_path_components.length, other.yaml_path_components.length) - 1; ++i) { + var c = yaml_path_components[i].compareTo(other.yaml_path_components[i]); + if (c != 0) { + return c; + } + } + return modify_yaml_path_for_sorting(yaml_path()).compareTo(modify_yaml_path_for_sorting(other.yaml_path())); + } + } + + protected void append_description(StringBuilder builder, String indent) { + final var description_wrapped = + indent + + "# " + + WordUtils.wrap(description.get(), Math.max(60, 80 - indent.length()), "\n" + indent + "# ", false); + builder.append(description_wrapped); + builder.append("\n"); + } + + protected void append_list_definition( + StringBuilder builder, + String indent, + String prefix, + Collection list, + Consumer2 append + ) { + list + .stream() + .forEach(i -> { + builder.append(indent); + builder.append(prefix); + builder.append(" - "); + append.apply(builder, i); + builder.append("\n"); + }); + } + + protected void append_value_range( + StringBuilder builder, + String indent, + U min, + U max, + U invalid_min, + U invalid_max + ) { + builder.append(indent); + builder.append("# Valid values: "); + if (!min.equals(invalid_min)) { + if (!max.equals(invalid_max)) { + builder.append("["); + builder.append(min); + builder.append(","); + builder.append(max); + builder.append("]"); + } else { + builder.append("["); + builder.append(min); + builder.append(",)"); + } + } else { + if (!max.equals(invalid_max)) { + builder.append("(,"); + builder.append(max); + builder.append("]"); + } else { + builder.append("Any " + type_name); + } + } + builder.append("\n"); + } + + protected void append_default_value(StringBuilder builder, String indent, Object def) { + builder.append(indent); + builder.append("# Default: "); + builder.append(def); + builder.append("\n"); + } + + protected void append_field_definition(StringBuilder builder, String indent, Object def) { + builder.append(indent); + builder.append(basename); + builder.append(": "); + builder.append(def); + builder.append("\n"); + } + + protected void check_yaml_path(YamlConfiguration yaml) throws YamlLoadException { + if (!yaml.contains(path, true)) { + throw new YamlLoadException("yaml is missing entry with path '" + path + "'"); + } + } + + public abstract T def(); + + // Disabled by default, fields must explicitly support this! + public boolean metrics() { + return false; + } + + public abstract void generate_yaml( + StringBuilder builder, + String indent, + YamlConfiguration existing_compatible_config + ); + + public abstract void check_loadable(YamlConfiguration yaml) throws YamlLoadException; + + public abstract void load(YamlConfiguration yaml); + + @SuppressWarnings("unchecked") + public T get() { + try { + return (T) field.get(owner); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } + + public void register_metrics(Metrics metrics) { + if (!metrics()) return; + metrics.addCustomChart(new SimplePie(yaml_path(), () -> get().toString())); + } + + public String[] components() { + return yaml_path_components; + } + + public int group_count() { + return yaml_path_components.length - 1; + } + + public static boolean same_group(ConfigField a, ConfigField b) { + if (a.yaml_path_components.length != b.yaml_path_components.length) { + return false; + } + for (int i = 0; i < a.yaml_path_components.length - 1; ++i) { + if (!a.yaml_path_components[i].equals(b.yaml_path_components[i])) { + return false; + } + } + return true; + } + + public static int common_group_count(ConfigField a, ConfigField b) { + int i; + for (i = 0; i < Math.min(a.yaml_path_components.length, b.yaml_path_components.length) - 1; ++i) { + if (!a.yaml_path_components[i].equals(b.yaml_path_components[i])) { + return i; + } + } + return i; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntField.java index 446237145..23ff3ab24 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntField.java @@ -8,74 +8,74 @@ public class ConfigIntField extends ConfigField { - public ConfigInt annotation; + public ConfigInt annotation; - public ConfigIntField(Object owner, Field field, Function map_name, ConfigInt annotation) { - super(owner, field, map_name, "int", annotation.desc()); - this.annotation = annotation; - } + public ConfigIntField(Object owner, Field field, Function map_name, ConfigInt annotation) { + super(owner, field, map_name, "int", annotation.desc()); + this.annotation = annotation; + } - @Override - public Integer def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return annotation.def(); - } - } + @Override + public Integer def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return annotation.def(); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_value_range(builder, indent, annotation.min(), annotation.max(), Integer.MIN_VALUE, Integer.MAX_VALUE); - append_default_value(builder, indent, def()); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition(builder, indent, def); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_value_range(builder, indent, annotation.min(), annotation.max(), Integer.MIN_VALUE, Integer.MAX_VALUE); + append_default_value(builder, indent, def()); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition(builder, indent, def); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!(yaml.get(yaml_path()) instanceof Number)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected int"); - } + if (!(yaml.get(yaml_path()) instanceof Number)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected int"); + } - final var val = yaml.getInt(yaml_path()); - if (annotation.min() != Integer.MIN_VALUE && val < annotation.min()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() - ); - } - if (annotation.max() != Integer.MAX_VALUE && val > annotation.max()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() - ); - } - } + final var val = yaml.getInt(yaml_path()); + if (annotation.min() != Integer.MIN_VALUE && val < annotation.min()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() + ); + } + if (annotation.max() != Integer.MAX_VALUE && val > annotation.max()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() + ); + } + } - public int load_from_yaml(YamlConfiguration yaml) { - return yaml.getInt(yaml_path()); - } + public int load_from_yaml(YamlConfiguration yaml) { + return yaml.getInt(yaml_path()); + } - public void load(YamlConfiguration yaml) { - try { - field.setInt(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.setInt(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntListField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntListField.java index 9b50e1e2d..e0ca99cad 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntListField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigIntListField.java @@ -12,97 +12,97 @@ public class ConfigIntListField extends ConfigField> { - public ConfigIntList annotation; - - public ConfigIntListField(Object owner, Field field, Function map_name, ConfigIntList annotation) { - super(owner, field, map_name, "int list", annotation.desc()); - this.annotation = annotation; - } - - private void append_int_list_definition(StringBuilder builder, String indent, String prefix, List def) { - append_list_definition(builder, indent, prefix, def, (b, i) -> b.append(i)); - } - - @Override - public List def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return Arrays.asList(ArrayUtils.toObject(annotation.def())); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_value_range(builder, indent, annotation.min(), annotation.max(), Integer.MIN_VALUE, Integer.MAX_VALUE); - - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_int_list_definition(builder, indent, "# ", def()); - - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_int_list_definition(builder, indent, "", def); - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isList(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); - } - - for (var obj : yaml.getList(yaml_path())) { - if (!(obj instanceof Number)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected int"); - } - - var val = yaml.getInt(yaml_path()); - if (annotation.min() != Integer.MIN_VALUE && val < annotation.min()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() - ); - } - if (annotation.max() != Integer.MAX_VALUE && val > annotation.max()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() - ); - } - } - } - - public List load_from_yaml(YamlConfiguration yaml) { - final var list = new ArrayList(); - for (var obj : yaml.getList(yaml_path())) { - list.add(((Number) obj).intValue()); - } - return list; - } - - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public ConfigIntList annotation; + + public ConfigIntListField(Object owner, Field field, Function map_name, ConfigIntList annotation) { + super(owner, field, map_name, "int list", annotation.desc()); + this.annotation = annotation; + } + + private void append_int_list_definition(StringBuilder builder, String indent, String prefix, List def) { + append_list_definition(builder, indent, prefix, def, (b, i) -> b.append(i)); + } + + @Override + public List def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return Arrays.asList(ArrayUtils.toObject(annotation.def())); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_value_range(builder, indent, annotation.min(), annotation.max(), Integer.MIN_VALUE, Integer.MAX_VALUE); + + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_int_list_definition(builder, indent, "# ", def()); + + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_int_list_definition(builder, indent, "", def); + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isList(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); + } + + for (var obj : yaml.getList(yaml_path())) { + if (!(obj instanceof Number)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected int"); + } + + var val = yaml.getInt(yaml_path()); + if (annotation.min() != Integer.MIN_VALUE && val < annotation.min()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() + ); + } + if (annotation.max() != Integer.MAX_VALUE && val > annotation.max()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() + ); + } + } + } + + public List load_from_yaml(YamlConfiguration yaml) { + final var list = new ArrayList(); + for (var obj : yaml.getList(yaml_path())) { + list.add(((Number) obj).intValue()); + } + return list; + } + + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigItemStackField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigItemStackField.java index 93e886f22..c6bdeba8a 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigItemStackField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigItemStackField.java @@ -12,160 +12,152 @@ public class ConfigItemStackField extends ConfigField { - public ConfigItemStack annotation; - - public ConfigItemStackField( - Object owner, - Field field, - Function map_name, - ConfigItemStack annotation - ) { - super(owner, field, map_name, "item stack", annotation.desc()); - this.annotation = annotation; - } - - private void append_item_stack_definition(StringBuilder builder, String indent, String prefix, ItemStack def) { - // Material - builder.append(indent); - builder.append(prefix); - builder.append(" material: "); - final var material = - "\"" + - escape_yaml(def.getType().getKey().getNamespace()) + - ":" + - escape_yaml(def.getType().getKey().getKey()) + - "\""; - builder.append(material); - builder.append("\n"); - - // Amount - if (def.getAmount() != 1) { - builder.append(indent); - builder.append(prefix); - builder.append(" amount: "); - builder.append(def.getAmount()); - builder.append("\n"); - } - } - - @Override - public ItemStack def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return new ItemStack(annotation.def().type(), annotation.def().amount()); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_item_stack_definition(builder, indent, "# ", def()); - - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_item_stack_definition(builder, indent, "", def); - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isConfigurationSection(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected group"); - } - - for (var var_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { - final var var_path = yaml_path() + "." + var_key; - switch (var_key) { - case "material": - { - if (!yaml.isString(var_path)) { - throw new YamlLoadException("Invalid type for yaml path '" + var_path + "', expected list"); - } - - final var str = yaml.getString(var_path); - final var split = str.split(":"); - if (split.length != 2) { - throw new YamlLoadException( - "Invalid material for yaml path '" + - yaml_path() + - "': '" + - str + - "' is not a valid namespaced key" - ); - } - break; - } - case "amount": - { - if (!(yaml.get(var_path) instanceof Number)) { - throw new YamlLoadException( - "Invalid type for yaml path '" + yaml_path() + "', expected int" - ); - } - final var val = yaml.getInt(yaml_path()); - if (val < 0) { - throw new YamlLoadException( - "Invalid value for yaml path '" + yaml_path() + "' Must be >= 0" - ); - } - break; - } - } - } - } - - public ItemStack load_from_yaml(YamlConfiguration yaml) { - var material_str = ""; - var amount = 1; - for (var var_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { - final var var_path = yaml_path() + "." + var_key; - switch (var_key) { - case "material": - { - amount = 0; - material_str = yaml.getString(var_path); - break; - } - case "amount": - { - amount = yaml.getInt(var_path); - break; - } - } - } - - final var split = material_str.split(":"); - final var material = material_from(namespaced_key(split[0], split[1])); - return new ItemStack(material, amount); - } - - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public ConfigItemStack annotation; + + public ConfigItemStackField( + Object owner, + Field field, + Function map_name, + ConfigItemStack annotation + ) { + super(owner, field, map_name, "item stack", annotation.desc()); + this.annotation = annotation; + } + + private void append_item_stack_definition(StringBuilder builder, String indent, String prefix, ItemStack def) { + // Material + builder.append(indent); + builder.append(prefix); + builder.append(" material: "); + final var material = + "\"" + + escape_yaml(def.getType().getKey().getNamespace()) + + ":" + + escape_yaml(def.getType().getKey().getKey()) + + "\""; + builder.append(material); + builder.append("\n"); + + // Amount + if (def.getAmount() != 1) { + builder.append(indent); + builder.append(prefix); + builder.append(" amount: "); + builder.append(def.getAmount()); + builder.append("\n"); + } + } + + @Override + public ItemStack def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return new ItemStack(annotation.def().type(), annotation.def().amount()); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_item_stack_definition(builder, indent, "# ", def()); + + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_item_stack_definition(builder, indent, "", def); + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isConfigurationSection(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected group"); + } + + for (var var_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { + final var var_path = yaml_path() + "." + var_key; + switch (var_key) { + case "material": { + if (!yaml.isString(var_path)) { + throw new YamlLoadException("Invalid type for yaml path '" + var_path + "', expected list"); + } + + final var str = yaml.getString(var_path); + final var split = str.split(":"); + if (split.length != 2) { + throw new YamlLoadException( + "Invalid material for yaml path '" + + yaml_path() + + "': '" + + str + + "' is not a valid namespaced key" + ); + } + break; + } + case "amount": { + if (!(yaml.get(var_path) instanceof Number)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected int"); + } + final var val = yaml.getInt(yaml_path()); + if (val < 0) { + throw new YamlLoadException("Invalid value for yaml path '" + yaml_path() + "' Must be >= 0"); + } + break; + } + } + } + } + + public ItemStack load_from_yaml(YamlConfiguration yaml) { + var material_str = ""; + var amount = 1; + for (var var_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { + final var var_path = yaml_path() + "." + var_key; + switch (var_key) { + case "material": { + amount = 0; + material_str = yaml.getString(var_path); + break; + } + case "amount": { + amount = yaml.getInt(var_path); + break; + } + } + } + + final var split = material_str.split(":"); + final var material = material_from(namespaced_key(split[0], split[1])); + return new ItemStack(material, amount); + } + + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigLongField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigLongField.java index 3644e303f..6f0375ca3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigLongField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigLongField.java @@ -8,74 +8,74 @@ public class ConfigLongField extends ConfigField { - public ConfigLong annotation; + public ConfigLong annotation; - public ConfigLongField(Object owner, Field field, Function map_name, ConfigLong annotation) { - super(owner, field, map_name, "long", annotation.desc()); - this.annotation = annotation; - } + public ConfigLongField(Object owner, Field field, Function map_name, ConfigLong annotation) { + super(owner, field, map_name, "long", annotation.desc()); + this.annotation = annotation; + } - @Override - public Long def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return annotation.def(); - } - } + @Override + public Long def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return annotation.def(); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_value_range(builder, indent, annotation.min(), annotation.max(), Long.MIN_VALUE, Long.MAX_VALUE); - append_default_value(builder, indent, def()); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition(builder, indent, def); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_value_range(builder, indent, annotation.min(), annotation.max(), Long.MIN_VALUE, Long.MAX_VALUE); + append_default_value(builder, indent, def()); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition(builder, indent, def); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!(yaml.get(yaml_path()) instanceof Number)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected long"); - } + if (!(yaml.get(yaml_path()) instanceof Number)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected long"); + } - var val = yaml.getLong(yaml_path()); - if (annotation.min() != Long.MIN_VALUE && val < annotation.min()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() - ); - } - if (annotation.max() != Long.MAX_VALUE && val > annotation.max()) { - throw new YamlLoadException( - "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() - ); - } - } + var val = yaml.getLong(yaml_path()); + if (annotation.min() != Long.MIN_VALUE && val < annotation.min()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be >= " + annotation.min() + ); + } + if (annotation.max() != Long.MAX_VALUE && val > annotation.max()) { + throw new YamlLoadException( + "Configuration '" + yaml_path() + "' has an invalid value: Value must be <= " + annotation.max() + ); + } + } - public long load_from_yaml(YamlConfiguration yaml) { - return yaml.getLong(yaml_path()); - } + public long load_from_yaml(YamlConfiguration yaml) { + return yaml.getLong(yaml_path()); + } - public void load(YamlConfiguration yaml) { - try { - field.setLong(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.setLong(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigManager.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigManager.java index 5977c4d53..200aad8b6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigManager.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -16,7 +15,6 @@ import java.util.Map; import java.util.function.Function; import java.util.logging.Level; -import java.util.stream.Collectors; import org.apache.commons.lang.WordUtils; import org.bstats.bukkit.Metrics; import org.bukkit.configuration.file.YamlConfiguration; @@ -42,317 +40,320 @@ public class ConfigManager { - private List> config_fields = new ArrayList<>(); - private Map section_descriptions = new HashMap<>(); - ConfigVersionField field_version; - Module module; - - public ConfigManager(Module module) { - this.module = module; - compile(module, s -> s); - } - - public long expected_version() { - return module.annotation.config_version(); - } - - private boolean has_config_annotation(Field field) { - for (var a : field.getAnnotations()) { - if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.config.Config")) { - return true; - } - } - return false; - } - - private void assert_field_prefix(Field field) { - if (!field.getName().startsWith("config_")) { - throw new RuntimeException("Configuration fields must be prefixed config_. This is a bug."); - } - } - - private ConfigField compile_field(Object owner, Field field, Function map_name) { - assert_field_prefix(field); - - // Get the annotation - Annotation annotation = null; - for (var a : field.getAnnotations()) { - if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.config.Config")) { - if (annotation == null) { - annotation = a; - } else { - throw new RuntimeException("Configuration fields must have exactly one @Config annotation."); - } - } - } - assert annotation != null; - final var atype = annotation.annotationType(); - - // Return a correct wrapper object - if (atype.equals(ConfigBoolean.class)) { - return new ConfigBooleanField(owner, field, map_name, (ConfigBoolean) annotation); - } else if (atype.equals(ConfigDict.class)) { - return new ConfigDictField(owner, field, map_name, (ConfigDict) annotation); - } else if (atype.equals(ConfigDouble.class)) { - return new ConfigDoubleField(owner, field, map_name, (ConfigDouble) annotation); - } else if (atype.equals(ConfigDoubleList.class)) { - return new ConfigDoubleListField(owner, field, map_name, (ConfigDoubleList) annotation); - } else if (atype.equals(ConfigExtendedMaterial.class)) { - return new ConfigExtendedMaterialField(owner, field, map_name, (ConfigExtendedMaterial) annotation); - } else if (atype.equals(ConfigInt.class)) { - return new ConfigIntField(owner, field, map_name, (ConfigInt) annotation); - } else if (atype.equals(ConfigIntList.class)) { - return new ConfigIntListField(owner, field, map_name, (ConfigIntList) annotation); - } else if (atype.equals(ConfigItemStack.class)) { - return new ConfigItemStackField(owner, field, map_name, (ConfigItemStack) annotation); - } else if (atype.equals(ConfigLong.class)) { - return new ConfigLongField(owner, field, map_name, (ConfigLong) annotation); - } else if (atype.equals(ConfigMaterial.class)) { - return new ConfigMaterialField(owner, field, map_name, (ConfigMaterial) annotation); - } else if (atype.equals(ConfigMaterialMapMapMap.class)) { - return new ConfigMaterialMapMapMapField(owner, field, map_name, (ConfigMaterialMapMapMap) annotation); - } else if (atype.equals(ConfigMaterialSet.class)) { - return new ConfigMaterialSetField(owner, field, map_name, (ConfigMaterialSet) annotation); - } else if (atype.equals(ConfigString.class)) { - return new ConfigStringField(owner, field, map_name, (ConfigString) annotation); - } else if (atype.equals(ConfigStringList.class)) { - return new ConfigStringListField(owner, field, map_name, (ConfigStringList) annotation); - } else if (atype.equals(ConfigStringListMap.class)) { - return new ConfigStringListMapField(owner, field, map_name, (ConfigStringListMap) annotation); - } else if (atype.equals(ConfigVersion.class)) { - if (owner != module) { - throw new RuntimeException("@ConfigVersion can only be used inside the main module. This is a bug."); - } - if (field_version != null) { - throw new RuntimeException( - "There must be exactly one @ConfigVersion field! (found multiple). This is a bug." - ); - } - return field_version = new ConfigVersionField(owner, field, map_name, (ConfigVersion) annotation); - } else { - throw new RuntimeException("Missing ConfigField handler for @" + atype.getName() + ". This is a bug."); - } - } - - private boolean verify_version(File file, long version) { - if (version != expected_version()) { - module.log.severe(file.getName() + ": expected version " + expected_version() + ", but got " + version); - - if (version == 0) { - module.log.severe("Something went wrong while generating or loading the configuration."); - module.log.severe("If you are sure your configuration is correct and this isn't a file"); - module.log.severe( - "system permission issue, please report this to https://github.com/oddlama/vane/issues" - ); - } else if (version < expected_version()) { - module.log.severe("This config is for an older version of " + module.getName() + "."); - module.log.severe("Please update your configuration. A new default configuration"); - module.log.severe("has been generated as 'config.yml.new'. Alternatively you can"); - module.log.severe("delete your configuration to have a new one generated next time."); - - generate_file(new File(module.getDataFolder(), "config.yml.new"), null); - } else { - module.log.severe("This config is for a future version of " + module.getName() + "."); - module.log.severe("Please use the correct file for this version, or delete it and"); - module.log.severe("it will be regenerated next time the server is started."); - } - - return false; - } - - return true; - } - - public void add_section_description(String yaml_path, String description) { - section_descriptions.put(yaml_path, description); - } - - @SuppressWarnings("unchecked") - public void compile(Object owner, Function map_name) { - // Compile all annotated fields - config_fields.addAll( - getAllFields(owner.getClass()) - .stream() - .filter(this::has_config_annotation) - .map(f -> compile_field(owner, f, map_name)).toList() - ); - - // Sort fields alphabetically, and by precedence (e.g., put a version last and lang first) - Collections.sort(config_fields); - - if (owner == module && field_version == null) { - throw new RuntimeException("There must be exactly one @ConfigVersion field! (found none). This is a bug."); - } - } - - private String indent_str(int level) { - return " ".repeat(level); - } - - public void generate_yaml(StringBuilder builder, YamlConfiguration existing_compatible_config) { - builder.append("# vim: set tabstop=2 softtabstop=0 expandtab shiftwidth=2:\n"); - builder.append("# This config file will automatically be updated, as long\n"); - builder.append("# as there are no incompatible changes between versions.\n"); - builder.append("# This means that additional comments will not be preserved!\n"); - - // Use the version field as a neutral field in the root group - ConfigField last_field = field_version; - var indent = ""; - - for (var f : config_fields) { - builder.append("\n"); - - if (!ConfigField.same_group(last_field, f)) { - final var new_indent_level = f.group_count(); - final var common_indent_level = ConfigField.common_group_count(last_field, f); - - // Build a full common section path - var section_path = ""; - for (int i = 0; i < common_indent_level; ++i) { - section_path = Context.append_yaml_path(section_path, f.components()[i], "."); - } - - // For each unopened section - for (int i = common_indent_level; i < new_indent_level; ++i) { - indent = indent_str(i); - - // Get a full section path - section_path = Context.append_yaml_path(section_path, f.components()[i], "."); - - // Append section description, if given. - final var section_desc = section_descriptions.get(section_path); - if (section_desc != null) { - final var description_wrapped = WordUtils.wrap( - section_desc, - Math.max(60, 80 - indent.length()), - "\n" + indent + "# ", - false - ); - builder.append(indent); - builder.append("# "); - builder.append(description_wrapped); - builder.append("\n"); - } - - // Append section - final var section_name = f.components()[i]; - builder.append(indent); - builder.append(section_name); - builder.append(":\n"); - } - - indent = indent_str(new_indent_level); - } - - // Append field yaml - f.generate_yaml(builder, indent, existing_compatible_config); - last_field = f; - } - } - - public File standard_file() { - return new File(module.getDataFolder(), "config.yml"); - } - - public boolean generate_file(File file, YamlConfiguration existing_compatible_config) { - final var builder = new StringBuilder(); - generate_yaml(builder, existing_compatible_config); - final var content = builder.toString(); - - // Save to tmp file, then move atomically to prevent corruption. - final var tmp_file = new File(file.getAbsolutePath() + ".tmp"); - try { - Files.writeString(tmp_file.toPath(), content); - } catch (IOException e) { - module.log.log(Level.SEVERE, "error while writing config file '" + file + "'", e); - return false; - } - - // Move atomically to prevent corruption. - try { - Files.move( - tmp_file.toPath(), - file.toPath(), - StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.ATOMIC_MOVE - ); - } catch (IOException e) { - module.log.log( - Level.SEVERE, - "error while atomically replacing '" + - file + - "' with temporary file (very recent changes might be lost)!", - e - ); - return false; - } - - return true; - } - - public boolean reload(File file) { - // Load file - var yaml = YamlConfiguration.loadConfiguration(file); - - // Check version - final var version = yaml.getLong("version", -1); - if (!verify_version(file, version)) { - return false; - } - - // Upgrade config to include all necessary keys (version-compatible extensions) - final var tmp_file = new File(module.getDataFolder(), "config.yml.tmp"); - if (!generate_file(tmp_file, yaml)) { - return false; - } - - // Move atomically to prevent corruption. - try { - Files.move( - tmp_file.toPath(), - standard_file().toPath(), - StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.ATOMIC_MOVE - ); - } catch (IOException e) { - module.log.log( - Level.SEVERE, - "error while atomically replacing '" + - standard_file() + - "' with updated version. Please manually resolve the conflict (new file is named '" + - tmp_file + - "')", - e - ); - return false; - } - - // Load newly written file - yaml = YamlConfiguration.loadConfiguration(file); - - try { - // Check configuration for errors - for (var f : config_fields) { - f.check_loadable(yaml); - } - - for (var f : config_fields) { - f.load(yaml); - } - } catch (YamlLoadException e) { - module.log.log(Level.SEVERE, "error while loading '" + file.getName() + "'", e); - return false; - } - - return true; - } - - public void register_metrics(Metrics metrics) { - // Track config values. Fields automatically know whether they want to be tracked or not via the annotation. - // By default, annotations use sensible defaults, so e.g., no strings will be tracked automatically, except - // when explicitly requested (e.g., language). - for (var f : config_fields) { - f.register_metrics(metrics); - } - } + private List> config_fields = new ArrayList<>(); + private Map section_descriptions = new HashMap<>(); + ConfigVersionField field_version; + Module module; + + public ConfigManager(Module module) { + this.module = module; + compile(module, s -> s); + } + + public long expected_version() { + return module.annotation.config_version(); + } + + private boolean has_config_annotation(Field field) { + for (var a : field.getAnnotations()) { + if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.config.Config")) { + return true; + } + } + return false; + } + + private void assert_field_prefix(Field field) { + if (!field.getName().startsWith("config_")) { + throw new RuntimeException("Configuration fields must be prefixed config_. This is a bug."); + } + } + + private ConfigField compile_field(Object owner, Field field, Function map_name) { + assert_field_prefix(field); + + // Get the annotation + Annotation annotation = null; + for (var a : field.getAnnotations()) { + if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.config.Config")) { + if (annotation == null) { + annotation = a; + } else { + throw new RuntimeException("Configuration fields must have exactly one @Config annotation."); + } + } + } + assert annotation != null; + final var atype = annotation.annotationType(); + + // Return a correct wrapper object + if (atype.equals(ConfigBoolean.class)) { + return new ConfigBooleanField(owner, field, map_name, (ConfigBoolean) annotation); + } else if (atype.equals(ConfigDict.class)) { + return new ConfigDictField(owner, field, map_name, (ConfigDict) annotation); + } else if (atype.equals(ConfigDouble.class)) { + return new ConfigDoubleField(owner, field, map_name, (ConfigDouble) annotation); + } else if (atype.equals(ConfigDoubleList.class)) { + return new ConfigDoubleListField(owner, field, map_name, (ConfigDoubleList) annotation); + } else if (atype.equals(ConfigExtendedMaterial.class)) { + return new ConfigExtendedMaterialField(owner, field, map_name, (ConfigExtendedMaterial) annotation); + } else if (atype.equals(ConfigInt.class)) { + return new ConfigIntField(owner, field, map_name, (ConfigInt) annotation); + } else if (atype.equals(ConfigIntList.class)) { + return new ConfigIntListField(owner, field, map_name, (ConfigIntList) annotation); + } else if (atype.equals(ConfigItemStack.class)) { + return new ConfigItemStackField(owner, field, map_name, (ConfigItemStack) annotation); + } else if (atype.equals(ConfigLong.class)) { + return new ConfigLongField(owner, field, map_name, (ConfigLong) annotation); + } else if (atype.equals(ConfigMaterial.class)) { + return new ConfigMaterialField(owner, field, map_name, (ConfigMaterial) annotation); + } else if (atype.equals(ConfigMaterialMapMapMap.class)) { + return new ConfigMaterialMapMapMapField(owner, field, map_name, (ConfigMaterialMapMapMap) annotation); + } else if (atype.equals(ConfigMaterialSet.class)) { + return new ConfigMaterialSetField(owner, field, map_name, (ConfigMaterialSet) annotation); + } else if (atype.equals(ConfigString.class)) { + return new ConfigStringField(owner, field, map_name, (ConfigString) annotation); + } else if (atype.equals(ConfigStringList.class)) { + return new ConfigStringListField(owner, field, map_name, (ConfigStringList) annotation); + } else if (atype.equals(ConfigStringListMap.class)) { + return new ConfigStringListMapField(owner, field, map_name, (ConfigStringListMap) annotation); + } else if (atype.equals(ConfigVersion.class)) { + if (owner != module) { + throw new RuntimeException("@ConfigVersion can only be used inside the main module. This is a bug."); + } + if (field_version != null) { + throw new RuntimeException( + "There must be exactly one @ConfigVersion field! (found multiple). This is a bug." + ); + } + return field_version = new ConfigVersionField(owner, field, map_name, (ConfigVersion) annotation); + } else { + throw new RuntimeException("Missing ConfigField handler for @" + atype.getName() + ". This is a bug."); + } + } + + private boolean verify_version(File file, long version) { + if (version != expected_version()) { + module.log.severe(file.getName() + ": expected version " + expected_version() + ", but got " + version); + + if (version == 0) { + module.log.severe("Something went wrong while generating or loading the configuration."); + module.log.severe("If you are sure your configuration is correct and this isn't a file"); + module.log.severe( + "system permission issue, please report this to https://github.com/oddlama/vane/issues" + ); + } else if (version < expected_version()) { + module.log.severe("This config is for an older version of " + module.getName() + "."); + module.log.severe("Please update your configuration. A new default configuration"); + module.log.severe("has been generated as 'config.yml.new'. Alternatively you can"); + module.log.severe("delete your configuration to have a new one generated next time."); + + generate_file(new File(module.getDataFolder(), "config.yml.new"), null); + } else { + module.log.severe("This config is for a future version of " + module.getName() + "."); + module.log.severe("Please use the correct file for this version, or delete it and"); + module.log.severe("it will be regenerated next time the server is started."); + } + + return false; + } + + return true; + } + + public void add_section_description(String yaml_path, String description) { + section_descriptions.put(yaml_path, description); + } + + @SuppressWarnings("unchecked") + public void compile(Object owner, Function map_name) { + // Compile all annotated fields + config_fields.addAll( + getAllFields(owner.getClass()) + .stream() + .filter(this::has_config_annotation) + .map(f -> compile_field(owner, f, map_name)) + .toList() + ); + + // Sort fields alphabetically, and by precedence (e.g., put a version last and lang first) + Collections.sort(config_fields); + + if (owner == module && field_version == null) { + throw new RuntimeException("There must be exactly one @ConfigVersion field! (found none). This is a bug."); + } + } + + private String indent_str(int level) { + return " ".repeat(level); + } + + public void generate_yaml(StringBuilder builder, YamlConfiguration existing_compatible_config) { + builder.append("# vim: set tabstop=2 softtabstop=0 expandtab shiftwidth=2:\n"); + builder.append("# This config file will automatically be updated, as long\n"); + builder.append("# as there are no incompatible changes between versions.\n"); + builder.append("# This means that additional comments will not be preserved!\n"); + + // Use the version field as a neutral field in the root group + ConfigField last_field = field_version; + var indent = ""; + + for (var f : config_fields) { + builder.append("\n"); + + if (!ConfigField.same_group(last_field, f)) { + final var new_indent_level = f.group_count(); + final var common_indent_level = ConfigField.common_group_count(last_field, f); + + // Build a full common section path + var section_path = ""; + for (int i = 0; i < common_indent_level; ++i) { + section_path = Context.append_yaml_path(section_path, f.components()[i], "."); + } + + // For each unopened section + for (int i = common_indent_level; i < new_indent_level; ++i) { + indent = indent_str(i); + + // Get a full section path + section_path = Context.append_yaml_path(section_path, f.components()[i], "."); + + // Append section description, if given. + final var section_desc = section_descriptions.get(section_path); + if (section_desc != null) { + final var description_wrapped = WordUtils.wrap( + section_desc, + Math.max(60, 80 - indent.length()), + "\n" + indent + "# ", + false + ); + builder.append(indent); + builder.append("# "); + builder.append(description_wrapped); + builder.append("\n"); + } + + // Append section + final var section_name = f.components()[i]; + builder.append(indent); + builder.append(section_name); + builder.append(":\n"); + } + + indent = indent_str(new_indent_level); + } + + // Append field yaml + f.generate_yaml(builder, indent, existing_compatible_config); + last_field = f; + } + } + + public File standard_file() { + return new File(module.getDataFolder(), "config.yml"); + } + + public boolean generate_file(File file, YamlConfiguration existing_compatible_config) { + final var builder = new StringBuilder(); + generate_yaml(builder, existing_compatible_config); + final var content = builder.toString(); + + // Save to tmp file, then move atomically to prevent corruption. + final var tmp_file = new File(file.getAbsolutePath() + ".tmp"); + try { + Files.writeString(tmp_file.toPath(), content); + } catch (IOException e) { + module.log.log(Level.SEVERE, "error while writing config file '" + file + "'", e); + return false; + } + + // Move atomically to prevent corruption. + try { + Files.move( + tmp_file.toPath(), + file.toPath(), + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE + ); + } catch (IOException e) { + module.log.log( + Level.SEVERE, + "error while atomically replacing '" + + file + + "' with temporary file (very recent changes might be lost)!", + e + ); + return false; + } + + return true; + } + + public boolean reload(File file) { + // Load file + var yaml = YamlConfiguration.loadConfiguration(file); + + // Check version + final var version = yaml.getLong("version", -1); + if (!verify_version(file, version)) { + return false; + } + + // Upgrade config to include all necessary keys (version-compatible extensions) + final var tmp_file = new File(module.getDataFolder(), "config.yml.tmp"); + if (!generate_file(tmp_file, yaml)) { + return false; + } + + // Move atomically to prevent corruption. + try { + Files.move( + tmp_file.toPath(), + standard_file().toPath(), + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE + ); + } catch (IOException e) { + module.log.log( + Level.SEVERE, + "error while atomically replacing '" + + standard_file() + + "' with updated version. Please manually resolve the conflict (new file is named '" + + tmp_file + + "')", + e + ); + return false; + } + + // Load newly written file + yaml = YamlConfiguration.loadConfiguration(file); + + try { + // Check configuration for errors + for (var f : config_fields) { + f.check_loadable(yaml); + } + + for (var f : config_fields) { + f.load(yaml); + } + } catch (YamlLoadException e) { + module.log.log(Level.SEVERE, "error while loading '" + file.getName() + "'", e); + return false; + } + + return true; + } + + public void register_metrics(Metrics metrics) { + // Track config values. Fields automatically know whether they want to be tracked or not via + // the annotation. + // By default, annotations use sensible defaults, so e.g., no strings will be tracked + // automatically, except + // when explicitly requested (e.g., language). + for (var f : config_fields) { + f.register_metrics(metrics); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialField.java index 430c18cb3..5227f0219 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialField.java @@ -12,90 +12,90 @@ public class ConfigMaterialField extends ConfigField { - public ConfigMaterial annotation; + public ConfigMaterial annotation; - public ConfigMaterialField( - Object owner, - Field field, - Function map_name, - ConfigMaterial annotation - ) { - super(owner, field, map_name, "material", annotation.desc()); - this.annotation = annotation; - } + public ConfigMaterialField( + Object owner, + Field field, + Function map_name, + ConfigMaterial annotation + ) { + super(owner, field, map_name, "material", annotation.desc()); + this.annotation = annotation; + } - @Override - public Material def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return annotation.def(); - } - } + @Override + public Material def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return annotation.def(); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_default_value( - builder, - indent, - "\"" + escape_yaml(def().getKey().getNamespace()) + ":" + escape_yaml(def().getKey().getKey()) + "\"" - ); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition( - builder, - indent, - "\"" + escape_yaml(def.getKey().getNamespace()) + ":" + escape_yaml(def.getKey().getKey()) + "\"" - ); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_default_value( + builder, + indent, + "\"" + escape_yaml(def().getKey().getNamespace()) + ":" + escape_yaml(def().getKey().getKey()) + "\"" + ); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition( + builder, + indent, + "\"" + escape_yaml(def.getKey().getNamespace()) + ":" + escape_yaml(def.getKey().getKey()) + "\"" + ); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isString(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); - } + if (!yaml.isString(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); + } - final var str = yaml.getString(yaml_path()); - final var split = str.split(":"); - if (split.length != 2) { - throw new YamlLoadException( - "Invalid material entry in list '" + yaml_path() + "': '" + str + "' is not a valid namespaced key" - ); - } + final var str = yaml.getString(yaml_path()); + final var split = str.split(":"); + if (split.length != 2) { + throw new YamlLoadException( + "Invalid material entry in list '" + yaml_path() + "': '" + str + "' is not a valid namespaced key" + ); + } - final var mat = material_from(namespaced_key(split[0], split[1])); - if (mat == null) { - throw new YamlLoadException( - "Invalid material entry in list '" + yaml_path() + "': '" + str + "' does not exist" - ); - } - } + final var mat = material_from(namespaced_key(split[0], split[1])); + if (mat == null) { + throw new YamlLoadException( + "Invalid material entry in list '" + yaml_path() + "': '" + str + "' does not exist" + ); + } + } - public Material load_from_yaml(YamlConfiguration yaml) { - final var split = yaml.getString(yaml_path()).split(":"); - return material_from(namespaced_key(split[0], split[1])); - } + public Material load_from_yaml(YamlConfiguration yaml) { + final var split = yaml.getString(yaml_path()).split(":"); + return material_from(namespaced_key(split[0], split[1])); + } - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialMapMapMapField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialMapMapMapField.java index 8974a00b4..38d72c76f 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialMapMapMapField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialMapMapMapField.java @@ -19,206 +19,196 @@ public class ConfigMaterialMapMapMapField extends ConfigField>>> { - public ConfigMaterialMapMapMap annotation; - - public ConfigMaterialMapMapMapField( - Object owner, - Field field, - Function map_name, - ConfigMaterialMapMapMap annotation - ) { - super( - owner, - field, - map_name, - "map of string to (map of string to (map of string to material))", - annotation.desc() - ); - this.annotation = annotation; - } - - private void append_map_definition( - StringBuilder builder, - String indent, - String prefix, - Map>> def - ) { - def - .entrySet() - .stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(e1 -> { - builder.append(indent); - builder.append(prefix); - builder.append(" "); - builder.append(escape_yaml(e1.getKey())); - builder.append(":\n"); - - e1 - .getValue() - .entrySet() - .stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(e2 -> { - builder.append(indent); - builder.append(prefix); - builder.append(" "); - builder.append(escape_yaml(e2.getKey())); - builder.append(":\n"); - - e2 - .getValue() - .entrySet() - .stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(e3 -> { - builder.append(indent); - builder.append(prefix); - builder.append(" "); - builder.append(escape_yaml(e3.getKey())); - builder.append(": \""); - builder.append(escape_yaml(e3.getValue().getKey().getNamespace())); - builder.append(":"); - builder.append(escape_yaml(e3.getValue().getKey().getKey())); - builder.append("\"\n"); - }); - }); - }); - } - - @Override - public Map>> def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return Arrays - .stream(annotation.def()) - .collect( - Collectors.toMap( - ConfigMaterialMapMapMapEntry::key, - e1 -> - Arrays - .stream(e1.value()) - .collect( - Collectors.toMap( - ConfigMaterialMapMapEntry::key, - e2 -> - Arrays - .stream(e2.value()) - .collect( - Collectors.toMap(ConfigMaterialMapEntry::key, e3 -> e3.value()) - ) - ) - ) - ) - ); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_map_definition(builder, indent, "# ", def()); - - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_map_definition(builder, indent, "", def); - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isConfigurationSection(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected group"); - } - - for (var key1 : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { - final var key1_path = yaml_path() + "." + key1; - if (!yaml.isConfigurationSection(key1_path)) { - throw new YamlLoadException("Invalid type for yaml path '" + key1_path + "', expected group"); - } - - for (var key2 : yaml.getConfigurationSection(key1_path).getKeys(false)) { - final var key2_path = key1_path + "." + key2; - if (!yaml.isConfigurationSection(key2_path)) { - throw new YamlLoadException("Invalid type for yaml path '" + key2_path + "', expected group"); - } - - for (var key3 : yaml.getConfigurationSection(key2_path).getKeys(false)) { - final var key3_path = key2_path + "." + key3; - if (!yaml.isString(key3_path)) { - throw new YamlLoadException("Invalid type for yaml path '" + key3_path + "', expected string"); - } - - final var str = yaml.getString(key3_path); - final var split = str.split(":"); - if (split.length != 2) { - throw new YamlLoadException( - "Invalid material entry in list '" + - key3_path + - "': '" + - str + - "' is not a valid namespaced key" - ); - } - - final var mat = material_from(namespaced_key(split[0], split[1])); - if (mat == null) { - throw new YamlLoadException( - "Invalid material entry in list '" + key3_path + "': '" + str + "' does not exist" - ); - } - } - } - } - } - - public Map>> load_from_yaml(YamlConfiguration yaml) { - final var map1 = new HashMap>>(); - for (final var key1 : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { - final var key1_path = yaml_path() + "." + key1; - final var map2 = new HashMap>(); - map1.put(key1, map2); - for (final var key2 : yaml.getConfigurationSection(key1_path).getKeys(false)) { - final var key2_path = key1_path + "." + key2; - final var map3 = new HashMap(); - map2.put(key2, map3); - for (final var key3 : yaml.getConfigurationSection(key2_path).getKeys(false)) { - final var key3_path = key2_path + "." + key3; - final var split = yaml.getString(key3_path).split(":"); - map3.put(key3, material_from(namespaced_key(split[0], split[1]))); - } - } - } - return map1; - } - - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public ConfigMaterialMapMapMap annotation; + + public ConfigMaterialMapMapMapField( + Object owner, + Field field, + Function map_name, + ConfigMaterialMapMapMap annotation + ) { + super( + owner, + field, + map_name, + "map of string to (map of string to (map of string to material))", + annotation.desc() + ); + this.annotation = annotation; + } + + private void append_map_definition( + StringBuilder builder, + String indent, + String prefix, + Map>> def + ) { + def + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e1 -> { + builder.append(indent); + builder.append(prefix); + builder.append(" "); + builder.append(escape_yaml(e1.getKey())); + builder.append(":\n"); + + e1 + .getValue() + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e2 -> { + builder.append(indent); + builder.append(prefix); + builder.append(" "); + builder.append(escape_yaml(e2.getKey())); + builder.append(":\n"); + + e2 + .getValue() + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e3 -> { + builder.append(indent); + builder.append(prefix); + builder.append(" "); + builder.append(escape_yaml(e3.getKey())); + builder.append(": \""); + builder.append(escape_yaml(e3.getValue().getKey().getNamespace())); + builder.append(":"); + builder.append(escape_yaml(e3.getValue().getKey().getKey())); + builder.append("\"\n"); + }); + }); + }); + } + + @Override + public Map>> def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return Arrays.stream(annotation.def()).collect( + Collectors.toMap(ConfigMaterialMapMapMapEntry::key, e1 -> + Arrays.stream(e1.value()).collect( + Collectors.toMap(ConfigMaterialMapMapEntry::key, e2 -> + Arrays.stream(e2.value()).collect( + Collectors.toMap(ConfigMaterialMapEntry::key, e3 -> e3.value()) + ) + ) + ) + ) + ); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_map_definition(builder, indent, "# ", def()); + + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_map_definition(builder, indent, "", def); + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isConfigurationSection(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected group"); + } + + for (var key1 : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { + final var key1_path = yaml_path() + "." + key1; + if (!yaml.isConfigurationSection(key1_path)) { + throw new YamlLoadException("Invalid type for yaml path '" + key1_path + "', expected group"); + } + + for (var key2 : yaml.getConfigurationSection(key1_path).getKeys(false)) { + final var key2_path = key1_path + "." + key2; + if (!yaml.isConfigurationSection(key2_path)) { + throw new YamlLoadException("Invalid type for yaml path '" + key2_path + "', expected group"); + } + + for (var key3 : yaml.getConfigurationSection(key2_path).getKeys(false)) { + final var key3_path = key2_path + "." + key3; + if (!yaml.isString(key3_path)) { + throw new YamlLoadException("Invalid type for yaml path '" + key3_path + "', expected string"); + } + + final var str = yaml.getString(key3_path); + final var split = str.split(":"); + if (split.length != 2) { + throw new YamlLoadException( + "Invalid material entry in list '" + + key3_path + + "': '" + + str + + "' is not a valid namespaced key" + ); + } + + final var mat = material_from(namespaced_key(split[0], split[1])); + if (mat == null) { + throw new YamlLoadException( + "Invalid material entry in list '" + key3_path + "': '" + str + "' does not exist" + ); + } + } + } + } + } + + public Map>> load_from_yaml(YamlConfiguration yaml) { + final var map1 = new HashMap>>(); + for (final var key1 : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { + final var key1_path = yaml_path() + "." + key1; + final var map2 = new HashMap>(); + map1.put(key1, map2); + for (final var key2 : yaml.getConfigurationSection(key1_path).getKeys(false)) { + final var key2_path = key1_path + "." + key2; + final var map3 = new HashMap(); + map2.put(key2, map3); + for (final var key3 : yaml.getConfigurationSection(key2_path).getKeys(false)) { + final var key3_path = key2_path + "." + key3; + final var split = yaml.getString(key3_path).split(":"); + map3.put(key3, material_from(namespaced_key(split[0], split[1]))); + } + } + } + return map1; + } + + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialSetField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialSetField.java index f708c1444..1ca02ed08 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialSetField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigMaterialSetField.java @@ -9,7 +9,6 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Function; - import org.bstats.bukkit.Metrics; import org.bstats.charts.AdvancedPie; import org.bukkit.Material; @@ -19,134 +18,130 @@ public class ConfigMaterialSetField extends ConfigField> { - public ConfigMaterialSet annotation; - - public ConfigMaterialSetField( - Object owner, - Field field, - Function map_name, - ConfigMaterialSet annotation - ) { - super(owner, field, map_name, "set of materials", annotation.desc()); - this.annotation = annotation; - } - - private void append_material_set_definition( - StringBuilder builder, - String indent, - String prefix, - Set def - ) { - append_list_definition( - builder, - indent, - prefix, - def, - (b, m) -> { - b.append("\""); - b.append(escape_yaml(m.getKey().getNamespace())); - b.append(":"); - b.append(escape_yaml(m.getKey().getKey())); - b.append("\""); - } - ); - } - - @Override - public Set def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return new HashSet<>(Arrays.asList(annotation.def())); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @Override - public void register_metrics(Metrics metrics) { - if (!this.metrics()) return; - metrics.addCustomChart(new AdvancedPie(yaml_path(), () -> { - final var values = new HashMap(); - for (final var v : get()) { - values.put(v.getKey().toString(), 1); - } - return values; - })); - } - - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_material_set_definition(builder, indent, "# ", def()); - - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_material_set_definition(builder, indent, "", def); - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isList(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); - } - - for (var obj : yaml.getList(yaml_path())) { - if (!(obj instanceof String)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); - } - - final var str = (String) obj; - final var split = str.split(":"); - if (split.length != 2) { - throw new YamlLoadException( - "Invalid material entry in list '" + yaml_path() + "': '" + str + "' is not a valid namespaced key" - ); - } - - final var mat = material_from(namespaced_key(split[0], split[1])); - if (mat == null) { - throw new YamlLoadException( - "Invalid material entry in list '" + yaml_path() + "': '" + str + "' does not exist" - ); - } - } - } - - public Set load_from_yaml(YamlConfiguration yaml) { - final var set = new HashSet(); - for (var obj : yaml.getList(yaml_path())) { - final var split = ((String) obj).split(":"); - set.add(material_from(namespaced_key(split[0], split[1]))); - } - return set; - } - - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public ConfigMaterialSet annotation; + + public ConfigMaterialSetField( + Object owner, + Field field, + Function map_name, + ConfigMaterialSet annotation + ) { + super(owner, field, map_name, "set of materials", annotation.desc()); + this.annotation = annotation; + } + + private void append_material_set_definition( + StringBuilder builder, + String indent, + String prefix, + Set def + ) { + append_list_definition(builder, indent, prefix, def, (b, m) -> { + b.append("\""); + b.append(escape_yaml(m.getKey().getNamespace())); + b.append(":"); + b.append(escape_yaml(m.getKey().getKey())); + b.append("\""); + }); + } + + @Override + public Set def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return new HashSet<>(Arrays.asList(annotation.def())); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @Override + public void register_metrics(Metrics metrics) { + if (!this.metrics()) return; + metrics.addCustomChart( + new AdvancedPie(yaml_path(), () -> { + final var values = new HashMap(); + for (final var v : get()) { + values.put(v.getKey().toString(), 1); + } + return values; + }) + ); + } + + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_material_set_definition(builder, indent, "# ", def()); + + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_material_set_definition(builder, indent, "", def); + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isList(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); + } + + for (var obj : yaml.getList(yaml_path())) { + if (!(obj instanceof String)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); + } + + final var str = (String) obj; + final var split = str.split(":"); + if (split.length != 2) { + throw new YamlLoadException( + "Invalid material entry in list '" + yaml_path() + "': '" + str + "' is not a valid namespaced key" + ); + } + + final var mat = material_from(namespaced_key(split[0], split[1])); + if (mat == null) { + throw new YamlLoadException( + "Invalid material entry in list '" + yaml_path() + "': '" + str + "' does not exist" + ); + } + } + } + + public Set load_from_yaml(YamlConfiguration yaml) { + final var set = new HashSet(); + for (var obj : yaml.getList(yaml_path())) { + final var split = ((String) obj).split(":"); + set.add(material_from(namespaced_key(split[0], split[1]))); + } + return set; + } + + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringField.java index 95ec52426..e92fef3a3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringField.java @@ -8,61 +8,61 @@ public class ConfigStringField extends ConfigField { - public ConfigString annotation; + public ConfigString annotation; - public ConfigStringField(Object owner, Field field, Function map_name, ConfigString annotation) { - super(owner, field, map_name, "string", annotation.desc()); - this.annotation = annotation; - } + public ConfigStringField(Object owner, Field field, Function map_name, ConfigString annotation) { + super(owner, field, map_name, "string", annotation.desc()); + this.annotation = annotation; + } - @Override - public String def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return annotation.def(); - } - } + @Override + public String def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return annotation.def(); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_default_value(builder, indent, "\"" + escape_yaml(def()) + "\""); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_field_definition(builder, indent, "\"" + escape_yaml(def) + "\""); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_default_value(builder, indent, "\"" + escape_yaml(def()) + "\""); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_field_definition(builder, indent, "\"" + escape_yaml(def) + "\""); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isString(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); - } - } + if (!yaml.isString(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); + } + } - public String load_from_yaml(YamlConfiguration yaml) { - return yaml.getString(yaml_path()); - } + public String load_from_yaml(YamlConfiguration yaml) { + return yaml.getString(yaml_path()); + } - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListField.java index dd46abe65..56510fbe0 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListField.java @@ -11,99 +11,93 @@ public class ConfigStringListField extends ConfigField> { - public ConfigStringList annotation; + public ConfigStringList annotation; - public ConfigStringListField( - Object owner, - Field field, - Function map_name, - ConfigStringList annotation - ) { - super(owner, field, map_name, "list of strings", annotation.desc()); - this.annotation = annotation; - } + public ConfigStringListField( + Object owner, + Field field, + Function map_name, + ConfigStringList annotation + ) { + super(owner, field, map_name, "list of strings", annotation.desc()); + this.annotation = annotation; + } - private void append_string_list_definition(StringBuilder builder, String indent, String prefix, List def) { - append_list_definition( - builder, - indent, - prefix, - def, - (b, s) -> { - b.append("\""); - b.append(escape_yaml(s)); - b.append("\""); - } - ); - } + private void append_string_list_definition(StringBuilder builder, String indent, String prefix, List def) { + append_list_definition(builder, indent, prefix, def, (b, s) -> { + b.append("\""); + b.append(escape_yaml(s)); + b.append("\""); + }); + } - @Override - public List def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return Arrays.asList(annotation.def()); - } - } + @Override + public List def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return Arrays.asList(annotation.def()); + } + } - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_string_list_definition(builder, indent, "# ", def()); + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_string_list_definition(builder, indent, "# ", def()); - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_string_list_definition(builder, indent, "", def); - } + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_string_list_definition(builder, indent, "", def); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isList(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); - } + if (!yaml.isList(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected list"); + } - for (final var obj : yaml.getList(yaml_path())) { - if (!(obj instanceof String)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); - } - } - } + for (final var obj : yaml.getList(yaml_path())) { + if (!(obj instanceof String)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected string"); + } + } + } - public List load_from_yaml(YamlConfiguration yaml) { - final var list = new ArrayList(); - for (var obj : yaml.getList(yaml_path())) { - list.add((String) obj); - } - return list; - } + public List load_from_yaml(YamlConfiguration yaml) { + final var list = new ArrayList(); + for (var obj : yaml.getList(yaml_path())) { + list.add((String) obj); + } + return list; + } - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListMapField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListMapField.java index 0c0fcb4c8..c4a3c1a12 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListMapField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigStringListMapField.java @@ -15,122 +15,122 @@ public class ConfigStringListMapField extends ConfigField>> { - public ConfigStringListMap annotation; - - public ConfigStringListMapField( - Object owner, - Field field, - Function map_name, - ConfigStringListMap annotation - ) { - super(owner, field, map_name, "map of string to string list", annotation.desc()); - this.annotation = annotation; - } - - private void append_string_list_map_definition( - StringBuilder builder, - String indent, - String prefix, - Map> def - ) { - def.forEach((k, list) -> { - builder.append(indent); - builder.append(prefix); - builder.append(" "); - builder.append(escape_yaml(k)); - builder.append(":\n"); - - list.forEach(s -> { - builder.append(indent); - builder.append(prefix); - builder.append(" - "); - builder.append(escape_yaml(s)); - builder.append("\n"); - }); - }); - } - - @Override - public Map> def() { - final var override = overridden_def(); - if (override != null) { - return override; - } else { - return Arrays - .stream(annotation.def()) - .collect(Collectors.toMap(ConfigStringListMapEntry::key, e -> Arrays.asList(e.list()))); - } - } - - @Override - public boolean metrics() { - final var override = overridden_metrics(); - if (override != null) { - return override; - } else { - return annotation.metrics(); - } - } - - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - - // Default - builder.append(indent); - builder.append("# Default:\n"); - append_string_list_map_definition(builder, indent, "# ", def()); - - // Definition - builder.append(indent); - builder.append(basename()); - builder.append(":\n"); - final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) - ? load_from_yaml(existing_compatible_config) - : def(); - append_string_list_map_definition(builder, indent, "", def); - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isConfigurationSection(yaml_path())) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected group"); - } - - for (var list_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { - final var list_path = yaml_path() + "." + list_key; - if (!yaml.isList(list_path)) { - throw new YamlLoadException("Invalid type for yaml path '" + list_path + "', expected list"); - } - - for (var obj : yaml.getList(list_path)) { - if (!(obj instanceof String)) { - throw new YamlLoadException("Invalid type for yaml path '" + list_path + "', expected string"); - } - } - } - } - - public Map> load_from_yaml(YamlConfiguration yaml) { - final var map = new HashMap>(); - for (final var list_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { - final var list_path = yaml_path() + "." + list_key; - final var list = new ArrayList(); - map.put(list_key, list); - for (final var obj : yaml.getList(list_path)) { - list.add((String) obj); - } - } - return map; - } - - public void load(YamlConfiguration yaml) { - try { - field.set(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public ConfigStringListMap annotation; + + public ConfigStringListMapField( + Object owner, + Field field, + Function map_name, + ConfigStringListMap annotation + ) { + super(owner, field, map_name, "map of string to string list", annotation.desc()); + this.annotation = annotation; + } + + private void append_string_list_map_definition( + StringBuilder builder, + String indent, + String prefix, + Map> def + ) { + def.forEach((k, list) -> { + builder.append(indent); + builder.append(prefix); + builder.append(" "); + builder.append(escape_yaml(k)); + builder.append(":\n"); + + list.forEach(s -> { + builder.append(indent); + builder.append(prefix); + builder.append(" - "); + builder.append(escape_yaml(s)); + builder.append("\n"); + }); + }); + } + + @Override + public Map> def() { + final var override = overridden_def(); + if (override != null) { + return override; + } else { + return Arrays.stream(annotation.def()).collect( + Collectors.toMap(ConfigStringListMapEntry::key, e -> Arrays.asList(e.list())) + ); + } + } + + @Override + public boolean metrics() { + final var override = overridden_metrics(); + if (override != null) { + return override; + } else { + return annotation.metrics(); + } + } + + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + + // Default + builder.append(indent); + builder.append("# Default:\n"); + append_string_list_map_definition(builder, indent, "# ", def()); + + // Definition + builder.append(indent); + builder.append(basename()); + builder.append(":\n"); + final var def = existing_compatible_config != null && existing_compatible_config.contains(yaml_path()) + ? load_from_yaml(existing_compatible_config) + : def(); + append_string_list_map_definition(builder, indent, "", def); + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isConfigurationSection(yaml_path())) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected group"); + } + + for (var list_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { + final var list_path = yaml_path() + "." + list_key; + if (!yaml.isList(list_path)) { + throw new YamlLoadException("Invalid type for yaml path '" + list_path + "', expected list"); + } + + for (var obj : yaml.getList(list_path)) { + if (!(obj instanceof String)) { + throw new YamlLoadException("Invalid type for yaml path '" + list_path + "', expected string"); + } + } + } + } + + public Map> load_from_yaml(YamlConfiguration yaml) { + final var map = new HashMap>(); + for (final var list_key : yaml.getConfigurationSection(yaml_path()).getKeys(false)) { + final var list_path = yaml_path() + "." + list_key; + final var list = new ArrayList(); + map.put(list_key, list); + for (final var obj : yaml.getList(list_path)) { + list.add((String) obj); + } + } + return map; + } + + public void load(YamlConfiguration yaml) { + try { + field.set(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigVersionField.java b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigVersionField.java index a01421f07..5dbaafa01 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigVersionField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/ConfigVersionField.java @@ -9,61 +9,61 @@ public class ConfigVersionField extends ConfigField { - public ConfigVersion annotation; + public ConfigVersion annotation; - public ConfigVersionField(Object owner, Field field, Function map_name, ConfigVersion annotation) { - super( - owner, - field, - map_name, - "version id", - "DO NOT CHANGE! The version of this config file. Used to determine if the config needs to be updated." - ); - this.annotation = annotation; + public ConfigVersionField(Object owner, Field field, Function map_name, ConfigVersion annotation) { + super( + owner, + field, + map_name, + "version id", + "DO NOT CHANGE! The version of this config file. Used to determine if the config needs to be updated." + ); + this.annotation = annotation; - // Version field should be at the bottom - this.sort_priority = 100; - } + // Version field should be at the bottom + this.sort_priority = 100; + } - @Override - public Long def() { - return null; - } + @Override + public Long def() { + return null; + } - @Override - public boolean metrics() { - return true; - } + @Override + public boolean metrics() { + return true; + } - @Override - public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { - append_description(builder, indent); - append_field_definition(builder, indent, ((Module) owner).annotation.config_version()); - } + @Override + public void generate_yaml(StringBuilder builder, String indent, YamlConfiguration existing_compatible_config) { + append_description(builder, indent); + append_field_definition(builder, indent, ((Module) owner).annotation.config_version()); + } - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!(yaml.get(yaml_path()) instanceof Number)) { - throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected long"); - } + if (!(yaml.get(yaml_path()) instanceof Number)) { + throw new YamlLoadException("Invalid type for yaml path '" + yaml_path() + "', expected long"); + } - var val = yaml.getLong(yaml_path()); - if (val < 1) { - throw new YamlLoadException("Configuration '" + yaml_path() + "' has an invalid value: Value must be >= 1"); - } - } + var val = yaml.getLong(yaml_path()); + if (val < 1) { + throw new YamlLoadException("Configuration '" + yaml_path() + "' has an invalid value: Value must be >= 1"); + } + } - public long load_from_yaml(YamlConfiguration yaml) { - return yaml.getLong(yaml_path()); - } + public long load_from_yaml(YamlConfiguration yaml) { + return yaml.getLong(yaml_path()); + } - public void load(YamlConfiguration yaml) { - try { - field.setLong(owner, load_from_yaml(yaml)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + public void load(YamlConfiguration yaml) { + try { + field.setLong(owner, load_from_yaml(yaml)); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/TranslatedItemStack.java b/vane-core/src/main/java/org/oddlama/vane/core/config/TranslatedItemStack.java index 9e5125cc5..de902431d 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/TranslatedItemStack.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/TranslatedItemStack.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.function.Consumer; - +import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; @@ -19,89 +19,87 @@ import org.oddlama.vane.core.module.Module; import org.oddlama.vane.core.module.ModuleComponent; -import net.kyori.adventure.text.Component; - public class TranslatedItemStack> extends ModuleComponent { - @ConfigInt(def = 1, min = 0, desc = "The item stack amount.") - public int config_amount; - - @ConfigExtendedMaterial( - def = "minecraft:barrier", - desc = "The item stack material. Also accepts heads from the head library or from defined custom items." - ) - public ExtendedMaterial config_material; - - @LangMessage - public TranslatedMessage lang_name; - - @LangMessageArray - public TranslatedMessageArray lang_lore; - - private ExtendedMaterial def_material; - private int def_amount; - - public TranslatedItemStack( - final Context context, - final String config_namespace, - final NamespacedKey def_material, - int def_amount, - final String desc - ) { - this(context, config_namespace, ExtendedMaterial.from(def_material), def_amount, desc); - } - - public TranslatedItemStack( - final Context context, - final String config_namespace, - final Material def_material, - int def_amount, - final String desc - ) { - this(context, config_namespace, ExtendedMaterial.from(def_material), def_amount, desc); - } - - public TranslatedItemStack( - final Context context, - final String config_namespace, - final ExtendedMaterial def_material, - int def_amount, - final String desc - ) { - super(context.namespace(config_namespace, desc)); - this.def_material = def_material; - this.def_amount = def_amount; - } - - public ItemStack item(Object... args) { - return name_item(config_material.item(config_amount), lang_name.format(args), lang_lore.format(args)); - } - - public ItemStack item_transform_lore(Consumer> f_lore, Object... args) { - final var lore = lang_lore.format(args); - f_lore.accept(lore); - return name_item(config_material.item(config_amount), lang_name.format(args), lore); - } - - public ItemStack item_amount(int amount, Object... args) { - return name_item(config_material.item(amount), lang_name.format(args), lang_lore.format(args)); - } - - public ItemStack alternative(final ItemStack alternative, Object... args) { - return name_item(alternative, lang_name.format(args), lang_lore.format(args)); - } - - public ExtendedMaterial config_material_def() { - return def_material; - } - - public int config_amount_def() { - return def_amount; - } - - @Override - public void on_enable() {} - - @Override - public void on_disable() {} + @ConfigInt(def = 1, min = 0, desc = "The item stack amount.") + public int config_amount; + + @ConfigExtendedMaterial( + def = "minecraft:barrier", + desc = "The item stack material. Also accepts heads from the head library or from defined custom items." + ) + public ExtendedMaterial config_material; + + @LangMessage + public TranslatedMessage lang_name; + + @LangMessageArray + public TranslatedMessageArray lang_lore; + + private ExtendedMaterial def_material; + private int def_amount; + + public TranslatedItemStack( + final Context context, + final String config_namespace, + final NamespacedKey def_material, + int def_amount, + final String desc + ) { + this(context, config_namespace, ExtendedMaterial.from(def_material), def_amount, desc); + } + + public TranslatedItemStack( + final Context context, + final String config_namespace, + final Material def_material, + int def_amount, + final String desc + ) { + this(context, config_namespace, ExtendedMaterial.from(def_material), def_amount, desc); + } + + public TranslatedItemStack( + final Context context, + final String config_namespace, + final ExtendedMaterial def_material, + int def_amount, + final String desc + ) { + super(context.namespace(config_namespace, desc)); + this.def_material = def_material; + this.def_amount = def_amount; + } + + public ItemStack item(Object... args) { + return name_item(config_material.item(config_amount), lang_name.format(args), lang_lore.format(args)); + } + + public ItemStack item_transform_lore(Consumer> f_lore, Object... args) { + final var lore = lang_lore.format(args); + f_lore.accept(lore); + return name_item(config_material.item(config_amount), lang_name.format(args), lore); + } + + public ItemStack item_amount(int amount, Object... args) { + return name_item(config_material.item(amount), lang_name.format(args), lang_lore.format(args)); + } + + public ItemStack alternative(final ItemStack alternative, Object... args) { + return name_item(alternative, lang_name.format(args), lang_lore.format(args)); + } + + public ExtendedMaterial config_material_def() { + return def_material; + } + + public int config_amount_def() { + return def_amount; + } + + @Override + public void on_enable() {} + + @Override + public void on_disable() {} } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootDefinition.java index 214285e0c..95487b2de 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootDefinition.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import org.bukkit.NamespacedKey; import org.bukkit.loot.LootTables; import org.oddlama.vane.core.LootTable.LootTableEntry; @@ -12,111 +11,126 @@ import org.oddlama.vane.util.StorageUtil; public class LootDefinition { - private static record Entry(double chance, int amount_min, int amount_max, String item_definition) { - public Object serialize() { - final HashMap dict = new HashMap<>(); - dict.put("chance", chance); - dict.put("amount_min", amount_min); - dict.put("amount_max", amount_max); - dict.put("item", item_definition); - return dict; - } - - public static Entry deserialize(Map map) { - if (!(map.get("chance") instanceof Double chance)) { - throw new IllegalArgumentException("Invalid loot table entry: chance must be a double!"); - } - - if (!(map.get("amount_min") instanceof Integer amount_min)) { - throw new IllegalArgumentException("Invalid loot table entry: amount_min must be a int!"); - } - - if (!(map.get("amount_max") instanceof Integer amount_max)) { - throw new IllegalArgumentException("Invalid loot table entry: amount_max must be a int!"); - } - - if (!(map.get("item") instanceof String item_definition)) { - throw new IllegalArgumentException("Invalid loot table entry: item_definition must be a String!"); - } - - return new Entry(chance, amount_min, amount_max, item_definition); - } - } - - public String name; - public List affected_tables = new ArrayList<>(); - public List entries = new ArrayList<>(); - - public LootDefinition(final String name) { - this.name = name; - } - - public String name() { - return name; - } - - public NamespacedKey key(final NamespacedKey base_key) { - return StorageUtil.namespaced_key(base_key.namespace(), base_key.value() + "." + name); - } - - public LootDefinition in(final NamespacedKey table) { - affected_tables.add(table); - return this; - } - - public LootDefinition in(final LootTables table) { - return in(table.getKey()); - } - - private LootDefinition add(Entry entry) { - entries.add(entry); - return this; - } - - public LootDefinition add(double chance, int amount_min, int amount_max, String item_definition) { - return add(new Entry(chance, amount_min, amount_max, item_definition)); - } - - public Object serialize() { - final HashMap dict = new HashMap<>(); - dict.put("tables", affected_tables.stream().map(NamespacedKey::toString).toList()); - dict.put("items", entries.stream().map(Entry::serialize).toList()); - return dict; - } - - @SuppressWarnings("unchecked") - public static LootDefinition deserialize(String name, Object raw_dict) { - if (!(raw_dict instanceof Map dict)) { - throw new IllegalArgumentException("Invalid loot table: Argument must be a Map, but is " + raw_dict.getClass() + "!"); - } - - final var table_dict = (Map)dict; - if (!(table_dict.get("tables") instanceof List tables)) { - throw new IllegalArgumentException("Invalid loot table: Argument must be a Map, but is " + raw_dict.getClass() + "!"); - } - - if (!(table_dict.get("items") instanceof List items)) { - throw new IllegalArgumentException("Invalid loot table: Argument must be a Map, but is " + raw_dict.getClass() + "!"); - } - - final var table = new LootDefinition(name); - for (final var e : tables) { - if (e instanceof String key) { - table.in(NamespacedKey.fromString(key)); - } - } - for (final var e : items) { - if (e instanceof Map map) { - table.add(Entry.deserialize((Map)map)); - } - } - - return table; - } - - public List entries() { - return entries.stream() - .map(e -> new LootTableEntry(e.chance, ItemUtil.itemstack_from_string(e.item_definition).getLeft(), e.amount_min, e.amount_max)) - .toList(); - } + + private static record Entry(double chance, int amount_min, int amount_max, String item_definition) { + public Object serialize() { + final HashMap dict = new HashMap<>(); + dict.put("chance", chance); + dict.put("amount_min", amount_min); + dict.put("amount_max", amount_max); + dict.put("item", item_definition); + return dict; + } + + public static Entry deserialize(Map map) { + if (!(map.get("chance") instanceof Double chance)) { + throw new IllegalArgumentException("Invalid loot table entry: chance must be a double!"); + } + + if (!(map.get("amount_min") instanceof Integer amount_min)) { + throw new IllegalArgumentException("Invalid loot table entry: amount_min must be a int!"); + } + + if (!(map.get("amount_max") instanceof Integer amount_max)) { + throw new IllegalArgumentException("Invalid loot table entry: amount_max must be a int!"); + } + + if (!(map.get("item") instanceof String item_definition)) { + throw new IllegalArgumentException("Invalid loot table entry: item_definition must be a String!"); + } + + return new Entry(chance, amount_min, amount_max, item_definition); + } + } + + public String name; + public List affected_tables = new ArrayList<>(); + public List entries = new ArrayList<>(); + + public LootDefinition(final String name) { + this.name = name; + } + + public String name() { + return name; + } + + public NamespacedKey key(final NamespacedKey base_key) { + return StorageUtil.namespaced_key(base_key.namespace(), base_key.value() + "." + name); + } + + public LootDefinition in(final NamespacedKey table) { + affected_tables.add(table); + return this; + } + + public LootDefinition in(final LootTables table) { + return in(table.getKey()); + } + + private LootDefinition add(Entry entry) { + entries.add(entry); + return this; + } + + public LootDefinition add(double chance, int amount_min, int amount_max, String item_definition) { + return add(new Entry(chance, amount_min, amount_max, item_definition)); + } + + public Object serialize() { + final HashMap dict = new HashMap<>(); + dict.put("tables", affected_tables.stream().map(NamespacedKey::toString).toList()); + dict.put("items", entries.stream().map(Entry::serialize).toList()); + return dict; + } + + @SuppressWarnings("unchecked") + public static LootDefinition deserialize(String name, Object raw_dict) { + if (!(raw_dict instanceof Map dict)) { + throw new IllegalArgumentException( + "Invalid loot table: Argument must be a Map, but is " + raw_dict.getClass() + "!" + ); + } + + final var table_dict = (Map) dict; + if (!(table_dict.get("tables") instanceof List tables)) { + throw new IllegalArgumentException( + "Invalid loot table: Argument must be a Map, but is " + raw_dict.getClass() + "!" + ); + } + + if (!(table_dict.get("items") instanceof List items)) { + throw new IllegalArgumentException( + "Invalid loot table: Argument must be a Map, but is " + raw_dict.getClass() + "!" + ); + } + + final var table = new LootDefinition(name); + for (final var e : tables) { + if (e instanceof String key) { + table.in(NamespacedKey.fromString(key)); + } + } + for (final var e : items) { + if (e instanceof Map map) { + table.add(Entry.deserialize((Map) map)); + } + } + + return table; + } + + public List entries() { + return entries + .stream() + .map(e -> + new LootTableEntry( + e.chance, + ItemUtil.itemstack_from_string(e.item_definition).getLeft(), + e.amount_min, + e.amount_max + ) + ) + .toList(); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTableList.java b/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTableList.java index 81301d1eb..68e4a7bfc 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTableList.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTableList.java @@ -5,30 +5,30 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import org.oddlama.vane.core.config.ConfigDictSerializable; public class LootTableList implements ConfigDictSerializable { - private List tables = new ArrayList<>(); - public List tables() { - return tables; - } + private List tables = new ArrayList<>(); + + public List tables() { + return tables; + } - public Map to_dict() { - return tables.stream().collect(Collectors.toMap(LootDefinition::name, LootDefinition::serialize)); - } + public Map to_dict() { + return tables.stream().collect(Collectors.toMap(LootDefinition::name, LootDefinition::serialize)); + } - public void from_dict(final Map dict) { - tables.clear(); - for (final var e : dict.entrySet()) { - tables.add(LootDefinition.deserialize(e.getKey(), e.getValue())); - } - } + public void from_dict(final Map dict) { + tables.clear(); + for (final var e : dict.entrySet()) { + tables.add(LootDefinition.deserialize(e.getKey(), e.getValue())); + } + } - public static LootTableList of(LootDefinition... defs) { - final var rl = new LootTableList(); - rl.tables = Arrays.asList(defs); - return rl; - } + public static LootTableList of(LootDefinition... defs) { + final var rl = new LootTableList(); + rl.tables = Arrays.asList(defs); + return rl; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTables.java b/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTables.java index 89e94c6a9..b6043937c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTables.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/loot/LootTables.java @@ -1,7 +1,6 @@ package org.oddlama.vane.core.config.loot; import java.util.function.Supplier; - import org.bukkit.NamespacedKey; import org.oddlama.vane.annotation.config.ConfigBoolean; import org.oddlama.vane.annotation.config.ConfigDict; @@ -10,54 +9,81 @@ import org.oddlama.vane.core.module.ModuleComponent; public class LootTables> extends ModuleComponent { - private final NamespacedKey base_loot_key; - - @ConfigBoolean(def = true, desc = "Whether the loot should be registered. Set to false to quickly disable all associated loot.") - public boolean config_register_loot; - - @ConfigDict(cls = LootTableList.class, desc = "") - private LootTableList config_loot; - - private Supplier def_loot; - private String desc; - - public LootTables(final Context context, final NamespacedKey base_loot_key, final Supplier def_loot) { - this(context, base_loot_key, def_loot, "The associated loot. This is a map of loot tables (as defined by minecraft) to additional loot. This additional loot is a list of loot definitions, which specify the amount and loot percentage for a particular item."); - } - - public LootTables(final Context context, final NamespacedKey base_loot_key, final Supplier def_loot, final String desc) { - super(context); - this.base_loot_key = base_loot_key; - this.def_loot = def_loot; - this.desc = desc; - } - - public LootTableList config_loot_def() { - return def_loot.get(); - } - - public String config_loot_desc() { - return desc; - } - - @Override - public void on_config_change() { - // Loot tables are processed in on_config_change and not in on_disable() / on_enable(), - // as the current loot table modifications need to be removed even if we are disabled afterwards. - config_loot.tables().forEach(table -> - table.affected_tables.forEach(table_key -> - get_module().loot_table(table_key).remove(table.key(base_loot_key)))); - if (enabled() && config_register_loot) { - config_loot.tables().forEach(table -> { - final var entries = table.entries(); - table.affected_tables.forEach(table_key -> get_module().loot_table(table_key).put(table.key(base_loot_key), entries)); - }); - } - } - - @Override - protected void on_enable() { } - - @Override - protected void on_disable() { } + + private final NamespacedKey base_loot_key; + + @ConfigBoolean( + def = true, + desc = "Whether the loot should be registered. Set to false to quickly disable all associated loot." + ) + public boolean config_register_loot; + + @ConfigDict(cls = LootTableList.class, desc = "") + private LootTableList config_loot; + + private Supplier def_loot; + private String desc; + + public LootTables( + final Context context, + final NamespacedKey base_loot_key, + final Supplier def_loot + ) { + this( + context, + base_loot_key, + def_loot, + "The associated loot. This is a map of loot tables (as defined by minecraft) to additional loot. This additional loot is a list of loot definitions, which specify the amount and loot percentage for a particular item." + ); + } + + public LootTables( + final Context context, + final NamespacedKey base_loot_key, + final Supplier def_loot, + final String desc + ) { + super(context); + this.base_loot_key = base_loot_key; + this.def_loot = def_loot; + this.desc = desc; + } + + public LootTableList config_loot_def() { + return def_loot.get(); + } + + public String config_loot_desc() { + return desc; + } + + @Override + public void on_config_change() { + // Loot tables are processed in on_config_change and not in on_disable() / on_enable(), + // as the current loot table modifications need to be removed even if we are disabled + // afterwards. + config_loot + .tables() + .forEach(table -> + table.affected_tables.forEach(table_key -> + get_module().loot_table(table_key).remove(table.key(base_loot_key)) + ) + ); + if (enabled() && config_register_loot) { + config_loot + .tables() + .forEach(table -> { + final var entries = table.entries(); + table.affected_tables.forEach(table_key -> + get_module().loot_table(table_key).put(table.key(base_loot_key), entries) + ); + }); + } + } + + @Override + protected void on_enable() {} + + @Override + protected void on_disable() {} } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/CookingRecipeDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/CookingRecipeDefinition.java index b988bc01a..c6649cd58 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/CookingRecipeDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/CookingRecipeDefinition.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; - import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; @@ -14,96 +13,106 @@ import org.oddlama.vane.util.ItemUtil; public class CookingRecipeDefinition extends RecipeDefinition { - private String input = null; - private String result = null; - private float experience = 0.0f; - private int cooking_time = 10; - private String type; - public CookingRecipeDefinition(String name, String type) { - super(name); - this.type = type; - switch (this.type) { - case "blasting": // fallthrough - case "furnace": // fallthrough - case "campfire": // fallthrough - case "smoking": break; - default: throw new IllegalArgumentException("Invalid cooking recipe: Unknown type '" + this.type + "'"); - } - } + private String input = null; + private String result = null; + private float experience = 0.0f; + private int cooking_time = 10; + private String type; + + public CookingRecipeDefinition(String name, String type) { + super(name); + this.type = type; + switch (this.type) { + case "blasting": // fallthrough + case "furnace": // fallthrough + case "campfire": // fallthrough + case "smoking": + break; + default: + throw new IllegalArgumentException("Invalid cooking recipe: Unknown type '" + this.type + "'"); + } + } - public CookingRecipeDefinition input(String input) { - this.input = input; - return this; - } + public CookingRecipeDefinition input(String input) { + this.input = input; + return this; + } - public CookingRecipeDefinition input(final Tag tag) { - return input("#" + tag.key()); - } + public CookingRecipeDefinition input(final Tag tag) { + return input("#" + tag.key()); + } - public CookingRecipeDefinition input(Material material) { - return input(material.key().toString()); - } + public CookingRecipeDefinition input(Material material) { + return input(material.key().toString()); + } - public CookingRecipeDefinition result(String result) { - this.result = result; - return this; - } + public CookingRecipeDefinition result(String result) { + this.result = result; + return this; + } - @Override - public Object to_dict() { - final HashMap dict = new HashMap<>(); - dict.put("cooking_time", this.cooking_time); - dict.put("experience", this.experience); - dict.put("input", this.input); - dict.put("result", this.result); - dict.put("type", type); - return dict; - } + @Override + public Object to_dict() { + final HashMap dict = new HashMap<>(); + dict.put("cooking_time", this.cooking_time); + dict.put("experience", this.experience); + dict.put("input", this.input); + dict.put("result", this.result); + dict.put("type", type); + return dict; + } - @Override - public RecipeDefinition from_dict(Object dict) { - if (!(dict instanceof Map)) { - throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: Argument must be a Map!"); - } - final var dict_map = (Map)dict; - if (dict_map.get("input") instanceof String input) { - this.input = input; - } else { - throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: input must be a string"); - } + @Override + public RecipeDefinition from_dict(Object dict) { + if (!(dict instanceof Map)) { + throw new IllegalArgumentException( + "Invalid " + type + " recipe dictionary: Argument must be a Map!" + ); + } + final var dict_map = (Map) dict; + if (dict_map.get("input") instanceof String input) { + this.input = input; + } else { + throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: input must be a string"); + } - if (dict_map.get("result") instanceof String result) { - this.result = result; - } else { - throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: result must be a string"); - } + if (dict_map.get("result") instanceof String result) { + this.result = result; + } else { + throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: result must be a string"); + } - if (dict_map.get("experience") instanceof Float experience) { - this.experience = experience; - } else { - throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: experience must be a float"); - } + if (dict_map.get("experience") instanceof Float experience) { + this.experience = experience; + } else { + throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: experience must be a float"); + } - if (dict_map.get("cooking_time") instanceof Integer cooking_time) { - this.cooking_time = cooking_time; - } else { - throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: cooking_time must be a int"); - } + if (dict_map.get("cooking_time") instanceof Integer cooking_time) { + this.cooking_time = cooking_time; + } else { + throw new IllegalArgumentException("Invalid " + type + " recipe dictionary: cooking_time must be a int"); + } - return this; - } + return this; + } - @Override - public Recipe to_recipe(NamespacedKey base_key) { - final var out = ItemUtil.itemstack_from_string(this.result).getLeft(); - final var in = RecipeDefinition.recipe_choice(input); - switch (this.type) { - case "blasting": return new BlastingRecipe(key(base_key), out, in, experience, cooking_time); - case "furnace": return new FurnaceRecipe(key(base_key), out, in, experience, cooking_time); - case "campfire": return new CampfireRecipe(key(base_key), out, in, experience, cooking_time); - case "smoking": return new SmokingRecipe(key(base_key), out, in, experience, cooking_time); - default: throw new IllegalArgumentException("Invalid cooking recipe: Unknown type '" + this.type + "'"); - } - } + @Override + public Recipe to_recipe(NamespacedKey base_key) { + final var out = ItemUtil.itemstack_from_string(this.result).getLeft(); + final var in = RecipeDefinition.recipe_choice(input); + switch (this.type) { + case "blasting": + return new BlastingRecipe(key(base_key), out, in, experience, cooking_time); + case "furnace": + return new FurnaceRecipe(key(base_key), out, in, experience, cooking_time); + case "campfire": + return new CampfireRecipe(key(base_key), out, in, experience, cooking_time); + case "smoking": + return new SmokingRecipe(key(base_key), out, in, experience, cooking_time); + default: + throw new IllegalArgumentException("Invalid cooking recipe: Unknown type '" + this.type + "'"); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeDefinition.java index 9c304cc2b..21568216a 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeDefinition.java @@ -6,7 +6,6 @@ import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; - import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; @@ -17,113 +16,127 @@ import org.oddlama.vane.util.StorageUtil; public abstract class RecipeDefinition { - public String name; - - public RecipeDefinition(final String name) { - this.name = name; - } - - public String name() { - return name; - } - - public NamespacedKey key(final NamespacedKey base_key) { - return StorageUtil.namespaced_key(base_key.namespace(), base_key.value() + "." + name); - } - - public abstract Recipe to_recipe(final NamespacedKey base_key); - public abstract Object to_dict(); - public abstract RecipeDefinition from_dict(Object dict); - - public static RecipeDefinition from_dict(final String name, final Object dict) { - if (!(dict instanceof Map)) { - throw new IllegalArgumentException("Invalid recipe dictionary: Argument must be a Map, but is " + dict.getClass() + "!"); - } - final var type = ((Map)dict).get("type"); - if (!(type instanceof String)) { - throw new IllegalArgumentException("Invalid recipe dictionary: recipe type must exist and be a string!"); - } - - final var str_type = (String)type; - switch (str_type) { - case "shaped": return new ShapedRecipeDefinition(name).from_dict(dict); - case "shapeless": return new ShapelessRecipeDefinition(name).from_dict(dict); - case "blasting": // fallthrough - case "furnace": // fallthrough - case "campfire": // fallthrough - case "smoking": return new CookingRecipeDefinition(name, str_type).from_dict(dict); - case "smithing": return new SmithingRecipeDefinition(name).from_dict(dict); - case "stonecutting": return new StonecuttingRecipeDefinition(name).from_dict(dict); - default: break; - } - - throw new IllegalArgumentException("Unknown recipe type '" + str_type + "'"); - } - - @SuppressWarnings("unchecked") - public static @NotNull RecipeChoice recipe_choice(String definition) { - definition = definition.strip(); - - // Try a material #tag - if (definition.startsWith("#")) { - for (final var f : Tag.class.getDeclaredFields()) { - if (Modifier.isStatic(f.getModifiers()) && f.getType() == Tag.class) { - try { - final var tag = (Tag)f.get(null); - if (tag == null) { - // System.out.println("warning: " + f + " has no associated key! It therefore cannot be used in custom recipes."); - continue; - } - if (tag.key().toString().equals(definition.substring(1))) { - return new RecipeChoice.MaterialChoice((Tag)tag); - } - } catch (IllegalArgumentException | IllegalAccessException e) { - throw new IllegalArgumentException("Invalid material tag: " + definition); - } - } - } - throw new IllegalArgumentException("Unknown material tag: " + definition); - } - - // Tuple of materials - if (definition.startsWith("(") && definition.endsWith(")")) { - final var parts = Arrays.stream(definition.substring(1, definition.length() - 1).split(",")) - .map(key -> { - final var mat = material_from(NamespacedKey.fromString(key.strip())); - if (mat == null) { - throw new IllegalArgumentException("Unknown material (only normal materials are allowed in tags): " + key); - } - return mat; - }) - .collect(Collectors.toList()); - return new RecipeChoice.MaterialChoice(parts); - } - - // Check if the amount is included - final var mult = definition.indexOf('*'); - int amount = 1; - if (mult != -1) { - final var amount_str = definition.substring(0, mult).strip(); - try { - amount = Integer.parseInt(amount_str); - if (amount <= 0) { - amount = 1; - } - - // Remove amount from definition for parsing - definition = definition.substring(mult + 1).strip(); - } catch (NumberFormatException e) { } - } - - // Exact choice of itemstack including NBT - final var item_stack_and_is_simple_mat = ItemUtil.itemstack_from_string(definition); - final var item_stack = item_stack_and_is_simple_mat.getLeft(); - final var is_simple_mat = item_stack_and_is_simple_mat.getRight(); - if (is_simple_mat && amount == 1) { - return new RecipeChoice.MaterialChoice(item_stack.getType()); - } - - item_stack.setAmount(amount); - return new RecipeChoice.ExactChoice(item_stack); - } + + public String name; + + public RecipeDefinition(final String name) { + this.name = name; + } + + public String name() { + return name; + } + + public NamespacedKey key(final NamespacedKey base_key) { + return StorageUtil.namespaced_key(base_key.namespace(), base_key.value() + "." + name); + } + + public abstract Recipe to_recipe(final NamespacedKey base_key); + + public abstract Object to_dict(); + + public abstract RecipeDefinition from_dict(Object dict); + + public static RecipeDefinition from_dict(final String name, final Object dict) { + if (!(dict instanceof Map)) { + throw new IllegalArgumentException( + "Invalid recipe dictionary: Argument must be a Map, but is " + dict.getClass() + "!" + ); + } + final var type = ((Map) dict).get("type"); + if (!(type instanceof String)) { + throw new IllegalArgumentException("Invalid recipe dictionary: recipe type must exist and be a string!"); + } + + final var str_type = (String) type; + switch (str_type) { + case "shaped": + return new ShapedRecipeDefinition(name).from_dict(dict); + case "shapeless": + return new ShapelessRecipeDefinition(name).from_dict(dict); + case "blasting": // fallthrough + case "furnace": // fallthrough + case "campfire": // fallthrough + case "smoking": + return new CookingRecipeDefinition(name, str_type).from_dict(dict); + case "smithing": + return new SmithingRecipeDefinition(name).from_dict(dict); + case "stonecutting": + return new StonecuttingRecipeDefinition(name).from_dict(dict); + default: + break; + } + + throw new IllegalArgumentException("Unknown recipe type '" + str_type + "'"); + } + + @SuppressWarnings("unchecked") + public static @NotNull RecipeChoice recipe_choice(String definition) { + definition = definition.strip(); + + // Try a material #tag + if (definition.startsWith("#")) { + for (final var f : Tag.class.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers()) && f.getType() == Tag.class) { + try { + final var tag = (Tag) f.get(null); + if (tag == null) { + // System.out.println("warning: " + f + " has no associated key! It + // therefore cannot be used in custom recipes."); + continue; + } + if (tag.key().toString().equals(definition.substring(1))) { + return new RecipeChoice.MaterialChoice((Tag) tag); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("Invalid material tag: " + definition); + } + } + } + throw new IllegalArgumentException("Unknown material tag: " + definition); + } + + // Tuple of materials + if (definition.startsWith("(") && definition.endsWith(")")) { + final var parts = Arrays.stream(definition.substring(1, definition.length() - 1).split(",")) + .map(key -> { + final var mat = material_from(NamespacedKey.fromString(key.strip())); + if (mat == null) { + throw new IllegalArgumentException( + "Unknown material (only normal materials are allowed in tags): " + key + ); + } + return mat; + }) + .collect(Collectors.toList()); + return new RecipeChoice.MaterialChoice(parts); + } + + // Check if the amount is included + final var mult = definition.indexOf('*'); + int amount = 1; + if (mult != -1) { + final var amount_str = definition.substring(0, mult).strip(); + try { + amount = Integer.parseInt(amount_str); + if (amount <= 0) { + amount = 1; + } + + // Remove amount from definition for parsing + definition = definition.substring(mult + 1).strip(); + } catch (NumberFormatException e) {} + } + + // Exact choice of itemstack including NBT + final var item_stack_and_is_simple_mat = ItemUtil.itemstack_from_string(definition); + final var item_stack = item_stack_and_is_simple_mat.getLeft(); + final var is_simple_mat = item_stack_and_is_simple_mat.getRight(); + if (is_simple_mat && amount == 1) { + return new RecipeChoice.MaterialChoice(item_stack.getType()); + } + + item_stack.setAmount(amount); + return new RecipeChoice.ExactChoice(item_stack); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeList.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeList.java index 22552c914..12e1e96e6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeList.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/RecipeList.java @@ -5,35 +5,36 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import org.oddlama.vane.core.config.ConfigDictSerializable; public class RecipeList implements ConfigDictSerializable { - private List recipes = new ArrayList<>(); - - public RecipeList() { } - public RecipeList(List recipes) { - this.recipes = recipes; - } - - public List recipes() { - return recipes; - } - - public Map to_dict() { - return recipes.stream().collect(Collectors.toMap(RecipeDefinition::name, RecipeDefinition::to_dict)); - } - - public void from_dict(final Map dict) { - recipes.clear(); - for (final var e : dict.entrySet()) { - recipes.add(RecipeDefinition.from_dict(e.getKey(), e.getValue())); - } - } - - public static RecipeList of(RecipeDefinition... defs) { - final var rl = new RecipeList(); - rl.recipes = Arrays.asList(defs); - return rl; - } + + private List recipes = new ArrayList<>(); + + public RecipeList() {} + + public RecipeList(List recipes) { + this.recipes = recipes; + } + + public List recipes() { + return recipes; + } + + public Map to_dict() { + return recipes.stream().collect(Collectors.toMap(RecipeDefinition::name, RecipeDefinition::to_dict)); + } + + public void from_dict(final Map dict) { + recipes.clear(); + for (final var e : dict.entrySet()) { + recipes.add(RecipeDefinition.from_dict(e.getKey(), e.getValue())); + } + } + + public static RecipeList of(RecipeDefinition... defs) { + final var rl = new RecipeList(); + rl.recipes = Arrays.asList(defs); + return rl; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/Recipes.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/Recipes.java index 7980ccc33..8920e1c9f 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/Recipes.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/Recipes.java @@ -1,7 +1,6 @@ package org.oddlama.vane.core.config.recipes; import java.util.function.Supplier; - import org.bukkit.NamespacedKey; import org.oddlama.vane.annotation.config.ConfigBoolean; import org.oddlama.vane.annotation.config.ConfigDict; @@ -10,49 +9,69 @@ import org.oddlama.vane.core.module.ModuleComponent; public class Recipes> extends ModuleComponent { - private final NamespacedKey base_recipe_key; - - @ConfigBoolean(def = true, desc = "Whether these recipes should be registered at all. Set to false to quickly disable all associated recipes.") - public boolean config_register_recipes; - - @ConfigDict(cls = RecipeList.class, desc = "") - private RecipeList config_recipes; - - private Supplier def_recipes; - private String desc; - - public Recipes(final Context context, final NamespacedKey base_recipe_key, final Supplier def_recipes) { - this(context, base_recipe_key, def_recipes, "The associated recipes. This is a map of recipe name to recipe definitions."); - } - - public Recipes(final Context context, final NamespacedKey base_recipe_key, final Supplier def_recipes, final String desc) { - super(context); - this.base_recipe_key = base_recipe_key; - this.def_recipes = def_recipes; - this.desc = desc; - } - - public RecipeList config_recipes_def() { - return def_recipes.get(); - } - - public String config_recipes_desc() { - return desc; - } - - @Override - public void on_config_change() { - // Recipes are processed in on_config_change and not in on_disable() / on_enable(), - // as the current recipes need to be removed even if we are disabled afterwards. - config_recipes.recipes().forEach(recipe -> get_module().getServer().removeRecipe(recipe.key(base_recipe_key))); - if (enabled() && config_register_recipes) { - config_recipes.recipes().forEach(recipe -> get_module().getServer().addRecipe(recipe.to_recipe(base_recipe_key))); - } - } - - @Override - protected void on_enable() { } - - @Override - protected void on_disable() { } + + private final NamespacedKey base_recipe_key; + + @ConfigBoolean( + def = true, + desc = "Whether these recipes should be registered at all. Set to false to quickly disable all associated recipes." + ) + public boolean config_register_recipes; + + @ConfigDict(cls = RecipeList.class, desc = "") + private RecipeList config_recipes; + + private Supplier def_recipes; + private String desc; + + public Recipes( + final Context context, + final NamespacedKey base_recipe_key, + final Supplier def_recipes + ) { + this( + context, + base_recipe_key, + def_recipes, + "The associated recipes. This is a map of recipe name to recipe definitions." + ); + } + + public Recipes( + final Context context, + final NamespacedKey base_recipe_key, + final Supplier def_recipes, + final String desc + ) { + super(context); + this.base_recipe_key = base_recipe_key; + this.def_recipes = def_recipes; + this.desc = desc; + } + + public RecipeList config_recipes_def() { + return def_recipes.get(); + } + + public String config_recipes_desc() { + return desc; + } + + @Override + public void on_config_change() { + // Recipes are processed in on_config_change and not in on_disable() / on_enable(), + // as the current recipes need to be removed even if we are disabled afterwards. + config_recipes.recipes().forEach(recipe -> get_module().getServer().removeRecipe(recipe.key(base_recipe_key))); + if (enabled() && config_register_recipes) { + config_recipes + .recipes() + .forEach(recipe -> get_module().getServer().addRecipe(recipe.to_recipe(base_recipe_key))); + } + } + + @Override + protected void on_enable() {} + + @Override + protected void on_disable() {} } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapedRecipeDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapedRecipeDefinition.java index 74e553500..eb1073b11 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapedRecipeDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapedRecipeDefinition.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; @@ -14,86 +13,94 @@ import org.oddlama.vane.util.ItemUtil; public class ShapedRecipeDefinition extends RecipeDefinition { - private List shape = new ArrayList<>(); - private Map ingredients = new HashMap<>(); - private String result = null; - public ShapedRecipeDefinition(String name) { - super(name); - } + private List shape = new ArrayList<>(); + private Map ingredients = new HashMap<>(); + private String result = null; + + public ShapedRecipeDefinition(String name) { + super(name); + } - public ShapedRecipeDefinition shape(String... shape) { - this.shape = List.of(shape); - return this; - } + public ShapedRecipeDefinition shape(String... shape) { + this.shape = List.of(shape); + return this; + } - public ShapedRecipeDefinition set_ingredient(char id, String ingredient) { - this.ingredients.put("" + id, ingredient); - return this; - } + public ShapedRecipeDefinition set_ingredient(char id, String ingredient) { + this.ingredients.put("" + id, ingredient); + return this; + } - public ShapedRecipeDefinition set_ingredient(char id, final Tag tag) { - return set_ingredient(id, "#" + tag.key()); - } + public ShapedRecipeDefinition set_ingredient(char id, final Tag tag) { + return set_ingredient(id, "#" + tag.key()); + } - public ShapedRecipeDefinition set_ingredient(char id, Material material) { - return set_ingredient(id, material.key().toString()); - } + public ShapedRecipeDefinition set_ingredient(char id, Material material) { + return set_ingredient(id, material.key().toString()); + } - public ShapedRecipeDefinition result(String result) { - this.result = result; - return this; - } + public ShapedRecipeDefinition result(String result) { + this.result = result; + return this; + } - @Override - public Object to_dict() { - final HashMap dict = new HashMap<>(); - dict.put("type", "shaped"); - dict.put("shape", this.shape); - dict.put("ingredients", this.ingredients); - dict.put("result", this.result); - return dict; - } + @Override + public Object to_dict() { + final HashMap dict = new HashMap<>(); + dict.put("type", "shaped"); + dict.put("shape", this.shape); + dict.put("ingredients", this.ingredients); + dict.put("result", this.result); + return dict; + } - @Override - public RecipeDefinition from_dict(Object dict) { - if (!(dict instanceof Map)) { - throw new IllegalArgumentException("Invalid shaped recipe dictionary: Argument must be a Map!"); - } - final var dict_map = (Map)dict; - if (dict_map.get("shape") instanceof List shape) { - this.shape = shape.stream().map(row -> (String)row).toList(); - if (this.shape.size() < 1 && this.shape.size() > 3) { - throw new IllegalArgumentException("Invalid shaped recipe dictionary: shape must be a list of 1 to 3 strings"); - } - } else { - throw new IllegalArgumentException("Invalid shaped recipe dictionary: shape must be a list of strings"); - } + @Override + public RecipeDefinition from_dict(Object dict) { + if (!(dict instanceof Map)) { + throw new IllegalArgumentException( + "Invalid shaped recipe dictionary: Argument must be a Map!" + ); + } + final var dict_map = (Map) dict; + if (dict_map.get("shape") instanceof List shape) { + this.shape = shape.stream().map(row -> (String) row).toList(); + if (this.shape.size() < 1 && this.shape.size() > 3) { + throw new IllegalArgumentException( + "Invalid shaped recipe dictionary: shape must be a list of 1 to 3 strings" + ); + } + } else { + throw new IllegalArgumentException("Invalid shaped recipe dictionary: shape must be a list of strings"); + } - if (dict_map.get("ingredients") instanceof Map ingredients) { - this.ingredients = ingredients.entrySet().stream() - .collect(Collectors.toMap( - e -> (String)e.getKey(), - e -> (String)e.getValue() - )); - } else { - throw new IllegalArgumentException("Invalid shaped recipe dictionary: ingredients must be a mapping of string to string"); - } + if (dict_map.get("ingredients") instanceof Map ingredients) { + this.ingredients = ingredients + .entrySet() + .stream() + .collect(Collectors.toMap(e -> (String) e.getKey(), e -> (String) e.getValue())); + } else { + throw new IllegalArgumentException( + "Invalid shaped recipe dictionary: ingredients must be a mapping of string to string" + ); + } - if (dict_map.get("result") instanceof String result) { - this.result = result; - } else { - throw new IllegalArgumentException("Invalid shaped recipe dictionary: result must be a string"); - } + if (dict_map.get("result") instanceof String result) { + this.result = result; + } else { + throw new IllegalArgumentException("Invalid shaped recipe dictionary: result must be a string"); + } - return this; - } + return this; + } - @Override - public Recipe to_recipe(NamespacedKey base_key) { - final var recipe = new ShapedRecipe(key(base_key), ItemUtil.itemstack_from_string(this.result).getLeft()); - recipe.shape(this.shape.toArray(new String[0])); - this.ingredients.forEach((name, definition) -> recipe.setIngredient(name.charAt(0), RecipeDefinition.recipe_choice(definition))); - return recipe; - } + @Override + public Recipe to_recipe(NamespacedKey base_key) { + final var recipe = new ShapedRecipe(key(base_key), ItemUtil.itemstack_from_string(this.result).getLeft()); + recipe.shape(this.shape.toArray(new String[0])); + this.ingredients.forEach((name, definition) -> + recipe.setIngredient(name.charAt(0), RecipeDefinition.recipe_choice(definition)) + ); + return recipe; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapelessRecipeDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapelessRecipeDefinition.java index db56b2721..341af73d3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapelessRecipeDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/ShapelessRecipeDefinition.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; @@ -13,65 +12,70 @@ import org.oddlama.vane.util.ItemUtil; public class ShapelessRecipeDefinition extends RecipeDefinition { - private List ingredients = new ArrayList<>(); - private String result = null; - public ShapelessRecipeDefinition(String name) { - super(name); - } + private List ingredients = new ArrayList<>(); + private String result = null; + + public ShapelessRecipeDefinition(String name) { + super(name); + } - public ShapelessRecipeDefinition add_ingredient(String ingredient) { - this.ingredients.add(ingredient); - return this; - } + public ShapelessRecipeDefinition add_ingredient(String ingredient) { + this.ingredients.add(ingredient); + return this; + } - public ShapelessRecipeDefinition add_ingredient(final Tag tag) { - return add_ingredient("#" + tag.key()); - } + public ShapelessRecipeDefinition add_ingredient(final Tag tag) { + return add_ingredient("#" + tag.key()); + } - public ShapelessRecipeDefinition add_ingredient(Material material) { - return add_ingredient(material.key().toString()); - } + public ShapelessRecipeDefinition add_ingredient(Material material) { + return add_ingredient(material.key().toString()); + } - public ShapelessRecipeDefinition result(String result) { - this.result = result; - return this; - } + public ShapelessRecipeDefinition result(String result) { + this.result = result; + return this; + } - @Override - public Object to_dict() { - final HashMap dict = new HashMap<>(); - dict.put("type", "shapeless"); - dict.put("ingredients", this.ingredients); - dict.put("result", this.result); - return dict; - } + @Override + public Object to_dict() { + final HashMap dict = new HashMap<>(); + dict.put("type", "shapeless"); + dict.put("ingredients", this.ingredients); + dict.put("result", this.result); + return dict; + } - @Override - public RecipeDefinition from_dict(Object dict) { - if (!(dict instanceof Map)) { - throw new IllegalArgumentException("Invalid shapeless recipe dictionary: Argument must be a Map!"); - } - final var dict_map = (Map)dict; - if (dict_map.get("ingredients") instanceof List ingredients) { - this.ingredients = ingredients.stream().map(i -> (String)i).toList(); - } else { - throw new IllegalArgumentException("Invalid shapeless recipe dictionary: ingredients must be a list of strings"); - } + @Override + public RecipeDefinition from_dict(Object dict) { + if (!(dict instanceof Map)) { + throw new IllegalArgumentException( + "Invalid shapeless recipe dictionary: Argument must be a Map!" + ); + } + final var dict_map = (Map) dict; + if (dict_map.get("ingredients") instanceof List ingredients) { + this.ingredients = ingredients.stream().map(i -> (String) i).toList(); + } else { + throw new IllegalArgumentException( + "Invalid shapeless recipe dictionary: ingredients must be a list of strings" + ); + } - if (dict_map.get("result") instanceof String result) { - this.result = result; - } else { - throw new IllegalArgumentException("Invalid shapeless recipe dictionary: result must be a string"); - } + if (dict_map.get("result") instanceof String result) { + this.result = result; + } else { + throw new IllegalArgumentException("Invalid shapeless recipe dictionary: result must be a string"); + } - return this; - } + return this; + } - @Override - public Recipe to_recipe(NamespacedKey base_key) { - final var recipe = new ShapelessRecipe(key(base_key), ItemUtil.itemstack_from_string(this.result).getLeft()); - this.ingredients.forEach(i -> recipe.addIngredient(RecipeDefinition.recipe_choice(i))); - return recipe; - } + @Override + public Recipe to_recipe(NamespacedKey base_key) { + final var recipe = new ShapelessRecipe(key(base_key), ItemUtil.itemstack_from_string(this.result).getLeft()); + this.ingredients.forEach(i -> recipe.addIngredient(RecipeDefinition.recipe_choice(i))); + return recipe; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/SmithingRecipeDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/SmithingRecipeDefinition.java index 547c5db89..e388be0b8 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/SmithingRecipeDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/SmithingRecipeDefinition.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; - import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; @@ -12,97 +11,107 @@ import org.oddlama.vane.util.ItemUtil; public class SmithingRecipeDefinition extends RecipeDefinition { - private String base = null; - private String addition = null; - private boolean copy_nbt = false; - private String result = null; - - public SmithingRecipeDefinition(String name) { - super(name); - } - - public SmithingRecipeDefinition base(String base) { - this.base = base; - return this; - } - - public SmithingRecipeDefinition base(final Tag tag) { - return base("#" + tag.key()); - } - - public SmithingRecipeDefinition base(Material material) { - return base(material.key().toString()); - } - - public SmithingRecipeDefinition addition(String addition) { - this.addition = addition; - return this; - } - - public SmithingRecipeDefinition copy_nbt(boolean copy_nbt) { - this.copy_nbt = copy_nbt; - return this; - } - - public SmithingRecipeDefinition addition(final Tag tag) { - return addition("#" + tag.key()); - } - - public SmithingRecipeDefinition addition(Material material) { - return addition(material.key().toString()); - } - - public SmithingRecipeDefinition result(String result) { - this.result = result; - return this; - } - - @Override - public Object to_dict() { - final HashMap dict = new HashMap<>(); - dict.put("base", this.base); - dict.put("addition", this.addition); - dict.put("copy_nbt", this.copy_nbt); - dict.put("result", this.result); - dict.put("type", "smithing"); - return dict; - } - - @Override - public RecipeDefinition from_dict(Object dict) { - if (!(dict instanceof Map)) { - throw new IllegalArgumentException("Invalid smithing recipe dictionary: Argument must be a Map!"); - } - final var dict_map = (Map)dict; - if (dict_map.get("base") instanceof String base) { - this.base = base; - } else { - throw new IllegalArgumentException("Invalid smithing recipe dictionary: base must be a string"); - } - - if (dict_map.get("addition") instanceof String addition) { - this.addition = addition; - } else { - throw new IllegalArgumentException("Invalid smithing recipe dictionary: addition must be a string"); - } - - if (dict_map.get("copy_nbt") instanceof Boolean copy_nbt) { - this.copy_nbt = copy_nbt; - } else { - throw new IllegalArgumentException("Invalid smithing recipe dictionary: copy_nbt must be a bool"); - } - - if (dict_map.get("result") instanceof String result) { - this.result = result; - } else { - throw new IllegalArgumentException("Invalid smithing recipe dictionary: result must be a string"); - } - - return this; - } - - @Override - public Recipe to_recipe(NamespacedKey base_key) { - return new SmithingTransformRecipe(key(base_key), ItemUtil.itemstack_from_string(this.result).getLeft(), new RecipeChoice.MaterialChoice(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE), RecipeDefinition.recipe_choice(base), RecipeDefinition.recipe_choice(addition), copy_nbt); - } + + private String base = null; + private String addition = null; + private boolean copy_nbt = false; + private String result = null; + + public SmithingRecipeDefinition(String name) { + super(name); + } + + public SmithingRecipeDefinition base(String base) { + this.base = base; + return this; + } + + public SmithingRecipeDefinition base(final Tag tag) { + return base("#" + tag.key()); + } + + public SmithingRecipeDefinition base(Material material) { + return base(material.key().toString()); + } + + public SmithingRecipeDefinition addition(String addition) { + this.addition = addition; + return this; + } + + public SmithingRecipeDefinition copy_nbt(boolean copy_nbt) { + this.copy_nbt = copy_nbt; + return this; + } + + public SmithingRecipeDefinition addition(final Tag tag) { + return addition("#" + tag.key()); + } + + public SmithingRecipeDefinition addition(Material material) { + return addition(material.key().toString()); + } + + public SmithingRecipeDefinition result(String result) { + this.result = result; + return this; + } + + @Override + public Object to_dict() { + final HashMap dict = new HashMap<>(); + dict.put("base", this.base); + dict.put("addition", this.addition); + dict.put("copy_nbt", this.copy_nbt); + dict.put("result", this.result); + dict.put("type", "smithing"); + return dict; + } + + @Override + public RecipeDefinition from_dict(Object dict) { + if (!(dict instanceof Map)) { + throw new IllegalArgumentException( + "Invalid smithing recipe dictionary: Argument must be a Map!" + ); + } + final var dict_map = (Map) dict; + if (dict_map.get("base") instanceof String base) { + this.base = base; + } else { + throw new IllegalArgumentException("Invalid smithing recipe dictionary: base must be a string"); + } + + if (dict_map.get("addition") instanceof String addition) { + this.addition = addition; + } else { + throw new IllegalArgumentException("Invalid smithing recipe dictionary: addition must be a string"); + } + + if (dict_map.get("copy_nbt") instanceof Boolean copy_nbt) { + this.copy_nbt = copy_nbt; + } else { + throw new IllegalArgumentException("Invalid smithing recipe dictionary: copy_nbt must be a bool"); + } + + if (dict_map.get("result") instanceof String result) { + this.result = result; + } else { + throw new IllegalArgumentException("Invalid smithing recipe dictionary: result must be a string"); + } + + return this; + } + + @Override + public Recipe to_recipe(NamespacedKey base_key) { + return new SmithingTransformRecipe( + key(base_key), + ItemUtil.itemstack_from_string(this.result).getLeft(), + new RecipeChoice.MaterialChoice(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE), + RecipeDefinition.recipe_choice(base), + RecipeDefinition.recipe_choice(addition), + copy_nbt + ); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/StonecuttingRecipeDefinition.java b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/StonecuttingRecipeDefinition.java index 5f9f4113d..6c847a8bb 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/StonecuttingRecipeDefinition.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/config/recipes/StonecuttingRecipeDefinition.java @@ -2,7 +2,6 @@ import java.util.HashMap; import java.util.Map; - import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; @@ -11,65 +10,68 @@ import org.oddlama.vane.util.ItemUtil; public class StonecuttingRecipeDefinition extends RecipeDefinition { - private String input = null; - private String result = null; - public StonecuttingRecipeDefinition(String name) { - super(name); - } + private String input = null; + private String result = null; + + public StonecuttingRecipeDefinition(String name) { + super(name); + } - public StonecuttingRecipeDefinition input(String input) { - this.input = input; - return this; - } + public StonecuttingRecipeDefinition input(String input) { + this.input = input; + return this; + } - public StonecuttingRecipeDefinition input(final Tag tag) { - return input("#" + tag.key()); - } + public StonecuttingRecipeDefinition input(final Tag tag) { + return input("#" + tag.key()); + } - public StonecuttingRecipeDefinition input(Material material) { - return input(material.key().toString()); - } + public StonecuttingRecipeDefinition input(Material material) { + return input(material.key().toString()); + } - public StonecuttingRecipeDefinition result(String result) { - this.result = result; - return this; - } + public StonecuttingRecipeDefinition result(String result) { + this.result = result; + return this; + } - @Override - public Object to_dict() { - final HashMap dict = new HashMap<>(); - dict.put("input", this.input); - dict.put("result", this.result); - dict.put("type", "stonecutting"); - return dict; - } + @Override + public Object to_dict() { + final HashMap dict = new HashMap<>(); + dict.put("input", this.input); + dict.put("result", this.result); + dict.put("type", "stonecutting"); + return dict; + } - @Override - public RecipeDefinition from_dict(Object dict) { - if (!(dict instanceof Map)) { - throw new IllegalArgumentException("Invalid stonecutting recipe dictionary: Argument must be a Map!"); - } - final var dict_map = (Map)dict; - if (dict_map.get("input") instanceof String input) { - this.input = input; - } else { - throw new IllegalArgumentException("Invalid stonecutting recipe dictionary: input must be a string"); - } + @Override + public RecipeDefinition from_dict(Object dict) { + if (!(dict instanceof Map)) { + throw new IllegalArgumentException( + "Invalid stonecutting recipe dictionary: Argument must be a Map!" + ); + } + final var dict_map = (Map) dict; + if (dict_map.get("input") instanceof String input) { + this.input = input; + } else { + throw new IllegalArgumentException("Invalid stonecutting recipe dictionary: input must be a string"); + } - if (dict_map.get("result") instanceof String result) { - this.result = result; - } else { - throw new IllegalArgumentException("Invalid stonecutting recipe dictionary: result must be a string"); - } + if (dict_map.get("result") instanceof String result) { + this.result = result; + } else { + throw new IllegalArgumentException("Invalid stonecutting recipe dictionary: result must be a string"); + } - return this; - } + return this; + } - @Override - public Recipe to_recipe(NamespacedKey base_key) { - final var out = ItemUtil.itemstack_from_string(this.result).getLeft(); - final var in = RecipeDefinition.recipe_choice(input); - return new StonecuttingRecipe(key(base_key), out, in); - } + @Override + public Recipe to_recipe(NamespacedKey base_key) { + final var out = ItemUtil.itemstack_from_string(this.result).getLeft(); + final var in = RecipeDefinition.recipe_choice(input); + return new StonecuttingRecipe(key(base_key), out, in); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/data/CooldownData.java b/vane-core/src/main/java/org/oddlama/vane/core/data/CooldownData.java index 9105d5624..0a2ca64b5 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/data/CooldownData.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/data/CooldownData.java @@ -7,42 +7,43 @@ public class CooldownData { - private final NamespacedKey key; - private final long cooldown_time; - - public CooldownData(NamespacedKey key, long cooldown_time) { - this.key = key; - this.cooldown_time = cooldown_time; - } - - /** - * Updates the cooldown data if and only if the cooldown has been exceeded. - * @return returns true if cooldown_time has been exceeded. - */ - public boolean check_or_update_cooldown(final PersistentDataHolder holder) { - final var persistent_data = holder.getPersistentDataContainer(); - final var last_time = persistent_data.getOrDefault(key, PersistentDataType.LONG, 0L); - final var now = System.currentTimeMillis(); - if (now - last_time < cooldown_time) { - return false; - } - - persistent_data.set(key, PersistentDataType.LONG, now); - return true; - } - - /** - * @return Gets the status of the cooldown without updating - */ - public boolean peek_cooldown(PersistentDataHolder holder) { - PersistentDataContainer persistent_data = holder.getPersistentDataContainer(); - Long last_time = persistent_data.getOrDefault(this.key, PersistentDataType.LONG, 0L); - long now = System.currentTimeMillis(); - return now - last_time >= this.cooldown_time; - } - - public void clear(final PersistentDataHolder holder) { - final var persistent_data = holder.getPersistentDataContainer(); - persistent_data.remove(key); - } + private final NamespacedKey key; + private final long cooldown_time; + + public CooldownData(NamespacedKey key, long cooldown_time) { + this.key = key; + this.cooldown_time = cooldown_time; + } + + /** + * Updates the cooldown data if and only if the cooldown has been exceeded. + * + * @return returns true if cooldown_time has been exceeded. + */ + public boolean check_or_update_cooldown(final PersistentDataHolder holder) { + final var persistent_data = holder.getPersistentDataContainer(); + final var last_time = persistent_data.getOrDefault(key, PersistentDataType.LONG, 0L); + final var now = System.currentTimeMillis(); + if (now - last_time < cooldown_time) { + return false; + } + + persistent_data.set(key, PersistentDataType.LONG, now); + return true; + } + + /** + * @return Gets the status of the cooldown without updating + */ + public boolean peek_cooldown(PersistentDataHolder holder) { + PersistentDataContainer persistent_data = holder.getPersistentDataContainer(); + Long last_time = persistent_data.getOrDefault(this.key, PersistentDataType.LONG, 0L); + long now = System.currentTimeMillis(); + return now - last_time >= this.cooldown_time; + } + + public void clear(final PersistentDataHolder holder) { + final var persistent_data = holder.getPersistentDataContainer(); + persistent_data.remove(key); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/enchantments/CustomEnchantment.java b/vane-core/src/main/java/org/oddlama/vane/core/enchantments/CustomEnchantment.java index 6cfb1455d..6c570b258 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/enchantments/CustomEnchantment.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/enchantments/CustomEnchantment.java @@ -1,10 +1,12 @@ package org.oddlama.vane.core.enchantments; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; - +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.EnchantmentTarget; @@ -23,235 +25,212 @@ import org.oddlama.vane.core.module.Module; import org.oddlama.vane.util.StorageUtil; -import io.papermc.paper.registry.RegistryAccess; -import io.papermc.paper.registry.RegistryKey; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; - public class CustomEnchantment> extends Listener { - // Track instances - private static final Map, CustomEnchantment> instances = new HashMap<>(); - - private VaneEnchantment annotation = getClass().getAnnotation(VaneEnchantment.class); - private String name; - private NamespacedKey key; - - public Recipes recipes; - public LootTables loot_tables; - - - // Language - @LangMessage - public TranslatedMessage lang_name; - - public CustomEnchantment(Context context) { - this(context, true); - } - - public CustomEnchantment(Context context, boolean default_enabled) { - super(null); - // Make namespace - name = annotation.name(); - context = context.group("enchantment_" + name, "Enable enchantment " + name, default_enabled); - set_context(context); - - // Create a namespaced key - key = StorageUtil.namespaced_key(get_module().namespace(), name); - - // Check if instance already exists - if (instances.get(getClass()) != null) { - throw new RuntimeException("Cannot create two instances of a custom enchantment!"); - } - instances.put(getClass(), this); - - // Automatic recipes and loot table config and registration - recipes = new Recipes(get_context(), this.key, this::default_recipes); - loot_tables = new LootTables(get_context(), this.key, this::default_loot_tables); - } - - /** - * Returns the bukkit wrapper for this enchantment. - */ - public final Enchantment bukkit() { - return RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).get(key); - } - - /** - * Returns the namespaced key for this enchantment. - */ - public final NamespacedKey key() { - return key; - } - - /** - * Only for internal use. - */ - final String get_name() { - return name; - } - - /** - * Returns the display format for the display name. - * By default, the color is dependent on the rarity. - * COMMON: gray - * UNCOMMON: dark blue - * RARE: gold - * VERY_RARE: bold dark purple - */ - public Component apply_display_format(Component component) { - switch (annotation.rarity()) { - default: - case COMMON: - case UNCOMMON: - return component.color(NamedTextColor.DARK_AQUA); - case RARE: - return component.color(NamedTextColor.GOLD); - case VERY_RARE: - return component.color(NamedTextColor.DARK_PURPLE).decorate(TextDecoration.BOLD); - } - } - - /** - * Determines the display name of the enchantment. - * Usually you don't need to override this method, as it already - * uses clientside translation keys and supports chat formatting. - */ - public Component display_name(int level) { - var display_name = apply_display_format(lang_name.format().decoration(TextDecoration.ITALIC, false)); - - if (level != 1 || max_level() != 1) { - final var chat_level = apply_display_format( - Component.translatable("enchantment.level." + level).decoration(TextDecoration.ITALIC, false) - ); - display_name = display_name.append(Component.text(" ")).append(chat_level); - } - - return display_name; - } - - /** - * The minimum level this enchantment can have. Always fixed to 1. - */ - public final int min_level() { - return 1; - } - - /** - * The maximum level this enchantment can have. - * Always reflects the annotation value {@link VaneEnchantment#max_level()}. - */ - public final int max_level() { - return annotation.max_level(); - } - - /** - * Determines the minimum enchanting table level at which this enchantment - * can occur at the given level. - */ - public int min_cost(int level) { - return 1 + level * 10; - } - - /** - * Determines the maximum enchanting table level at which this enchantment - * can occur at the given level. - */ - public int max_cost(int level) { - return min_cost(level) + 5; - } - - /** - * Determines if this enchantment can be obtained with the enchanting table. - * Always reflects the annotation value {@link VaneEnchantment#treasure()}. - */ - public final boolean is_treasure() { - return annotation.treasure(); - } - - /** - * Determines if this enchantment is tradeable with villagers. - * Always reflects the annotation value {@link VaneEnchantment#tradeable()}. - */ - public final boolean is_tradeable() { - return annotation.tradeable(); - } - - /** - * Determines if this enchantment is a curse. - * Always reflects the annotation value {@link VaneEnchantment#curse()}. - */ - public final boolean is_curse() { - return annotation.curse(); - } - - /** - * Determines if this enchantment generates on treasure items. - * Always reflects the annotation value {@link VaneEnchantment#generate_in_treasure()}. - */ - public final boolean generate_in_treasure() { - return annotation.generate_in_treasure(); - } - - /** - * Determines which item types this enchantment can be applied to. - * {@link #can_enchant(ItemStack)} can be used to further limit the applicable items. - * Always reflects the annotation value {@link VaneEnchantment#target()}. - */ - public final EnchantmentTarget target() { - return annotation.target(); - } - - /** - * Determines the enchantment rarity. - * Always reflects the annotation value {@link VaneEnchantment#rarity()}. - */ - public final Rarity rarity() { - return annotation.rarity(); - } - - /** - * Weather custom items are allowed to be enchanted with this enchantment. - */ - public final boolean allow_custom() { - return annotation.allow_custom(); - } - - /** - * Determines if this enchantment is compatible with the given enchantment. - * By default, all enchantments are compatible. Override this if you want - * to express conflicting enchantments. - */ - public boolean is_compatible(@NotNull Enchantment other) { - return true; - } - - /** - * Determines if this enchantment can be applied to the given item. - * By default, this returns true if the {@link #target()} category includes - * the given itemstack. - * Unfortunately, this method cannot be used to widen - * the allowed items, just to narrow it (limitation due to minecraft server internals). - * So for best results, always check super.can_enchant first when overriding. - */ - public boolean can_enchant(@NotNull ItemStack item_stack) { - return annotation.target().includes(item_stack); - } - - public RecipeList default_recipes() { - return RecipeList.of(); - } - - public LootTableList default_loot_tables() { - return LootTableList.of(); - } - - /** Applies this enchantment to the given string item definition. */ - protected String on(String item_definition) { - return on(item_definition, 1); - } - - protected String on(String item_definition, int level) { - return item_definition + "#enchants{" + key + "*" + level + "}"; - } + + // Track instances + private static final Map, CustomEnchantment> instances = new HashMap<>(); + + private VaneEnchantment annotation = getClass().getAnnotation(VaneEnchantment.class); + private String name; + private NamespacedKey key; + + public Recipes recipes; + public LootTables loot_tables; + + // Language + @LangMessage + public TranslatedMessage lang_name; + + public CustomEnchantment(Context context) { + this(context, true); + } + + public CustomEnchantment(Context context, boolean default_enabled) { + super(null); + // Make namespace + name = annotation.name(); + context = context.group("enchantment_" + name, "Enable enchantment " + name, default_enabled); + set_context(context); + + // Create a namespaced key + key = StorageUtil.namespaced_key(get_module().namespace(), name); + + // Check if instance already exists + if (instances.get(getClass()) != null) { + throw new RuntimeException("Cannot create two instances of a custom enchantment!"); + } + instances.put(getClass(), this); + + // Automatic recipes and loot table config and registration + recipes = new Recipes(get_context(), this.key, this::default_recipes); + loot_tables = new LootTables(get_context(), this.key, this::default_loot_tables); + } + + /** Returns the bukkit wrapper for this enchantment. */ + public final Enchantment bukkit() { + return RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).get(key); + } + + /** Returns the namespaced key for this enchantment. */ + public final NamespacedKey key() { + return key; + } + + /** Only for internal use. */ + final String get_name() { + return name; + } + + /** + * Returns the display format for the display name. By default, the color is dependent on the + * rarity. COMMON: gray UNCOMMON: dark blue RARE: gold VERY_RARE: bold dark purple + */ + public Component apply_display_format(Component component) { + switch (annotation.rarity()) { + default: + case COMMON: + case UNCOMMON: + return component.color(NamedTextColor.DARK_AQUA); + case RARE: + return component.color(NamedTextColor.GOLD); + case VERY_RARE: + return component.color(NamedTextColor.DARK_PURPLE).decorate(TextDecoration.BOLD); + } + } + + /** + * Determines the display name of the enchantment. Usually you don't need to override this + * method, as it already uses clientside translation keys and supports chat formatting. + */ + public Component display_name(int level) { + var display_name = apply_display_format(lang_name.format().decoration(TextDecoration.ITALIC, false)); + + if (level != 1 || max_level() != 1) { + final var chat_level = apply_display_format( + Component.translatable("enchantment.level." + level).decoration(TextDecoration.ITALIC, false) + ); + display_name = display_name.append(Component.text(" ")).append(chat_level); + } + + return display_name; + } + + /** The minimum level this enchantment can have. Always fixed to 1. */ + public final int min_level() { + return 1; + } + + /** + * The maximum level this enchantment can have. Always reflects the annotation value {@link + * VaneEnchantment#max_level()}. + */ + public final int max_level() { + return annotation.max_level(); + } + + /** + * Determines the minimum enchanting table level at which this enchantment can occur at the + * given level. + */ + public int min_cost(int level) { + return 1 + level * 10; + } + + /** + * Determines the maximum enchanting table level at which this enchantment can occur at the + * given level. + */ + public int max_cost(int level) { + return min_cost(level) + 5; + } + + /** + * Determines if this enchantment can be obtained with the enchanting table. Always reflects the + * annotation value {@link VaneEnchantment#treasure()}. + */ + public final boolean is_treasure() { + return annotation.treasure(); + } + + /** + * Determines if this enchantment is tradeable with villagers. Always reflects the annotation + * value {@link VaneEnchantment#tradeable()}. + */ + public final boolean is_tradeable() { + return annotation.tradeable(); + } + + /** + * Determines if this enchantment is a curse. Always reflects the annotation value {@link + * VaneEnchantment#curse()}. + */ + public final boolean is_curse() { + return annotation.curse(); + } + + /** + * Determines if this enchantment generates on treasure items. Always reflects the annotation + * value {@link VaneEnchantment#generate_in_treasure()}. + */ + public final boolean generate_in_treasure() { + return annotation.generate_in_treasure(); + } + + /** + * Determines which item types this enchantment can be applied to. {@link + * #can_enchant(ItemStack)} can be used to further limit the applicable items. Always reflects + * the annotation value {@link VaneEnchantment#target()}. + */ + public final EnchantmentTarget target() { + return annotation.target(); + } + + /** + * Determines the enchantment rarity. Always reflects the annotation value {@link + * VaneEnchantment#rarity()}. + */ + public final Rarity rarity() { + return annotation.rarity(); + } + + /** Weather custom items are allowed to be enchanted with this enchantment. */ + public final boolean allow_custom() { + return annotation.allow_custom(); + } + + /** + * Determines if this enchantment is compatible with the given enchantment. By default, all + * enchantments are compatible. Override this if you want to express conflicting enchantments. + */ + public boolean is_compatible(@NotNull Enchantment other) { + return true; + } + + /** + * Determines if this enchantment can be applied to the given item. By default, this returns + * true if the {@link #target()} category includes the given itemstack. Unfortunately, this + * method cannot be used to widen the allowed items, just to narrow it (limitation due to + * minecraft server internals). So for best results, always check super.can_enchant first when + * overriding. + */ + public boolean can_enchant(@NotNull ItemStack item_stack) { + return annotation.target().includes(item_stack); + } + + public RecipeList default_recipes() { + return RecipeList.of(); + } + + public LootTableList default_loot_tables() { + return LootTableList.of(); + } + + /** Applies this enchantment to the given string item definition. */ + protected String on(String item_definition) { + return on(item_definition, 1); + } + + protected String on(String item_definition, int level) { + return item_definition + "#enchants{" + key + "*" + level + "}"; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/enchantments/EnchantmentManager.java b/vane-core/src/main/java/org/oddlama/vane/core/enchantments/EnchantmentManager.java index e318ada22..561eb908e 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/enchantments/EnchantmentManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/enchantments/EnchantmentManager.java @@ -1,182 +1,175 @@ package org.oddlama.vane.core.enchantments; +import com.destroystokyo.paper.event.inventory.PrepareResultEvent; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashMap; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import com.destroystokyo.paper.event.inventory.PrepareResultEvent; - +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; import org.bukkit.NamespacedKey; import org.bukkit.enchantments.Enchantment; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.enchantment.EnchantItemEvent; -import org.bukkit.event.entity.VillagerAcquireTradeEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.world.LootGenerateEvent; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Merchant; import org.bukkit.inventory.MerchantRecipe; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.oddlama.vane.core.Core; import org.oddlama.vane.core.Listener; import org.oddlama.vane.core.module.Context; import org.oddlama.vane.util.ItemUtil; import org.oddlama.vane.util.StorageUtil; -import org.bukkit.craftbukkit.enchantments.CraftEnchantment; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TranslatableComponent; - public class EnchantmentManager extends Listener { - private static final NamespacedKey SENTINEL = StorageUtil.namespaced_key("vane", "enchantment_lore"); - - public EnchantmentManager(Context context) { - super(context); - } - - public ItemStack update_enchanted_item(ItemStack item_stack) { - return update_enchanted_item(item_stack, new HashMap(), false); - } - - public ItemStack update_enchanted_item(ItemStack item_stack, Map additional_enchantments) { - return update_enchanted_item(item_stack, additional_enchantments, false); - } - - public ItemStack update_enchanted_item(ItemStack item_stack, boolean only_if_enchanted) { - return update_enchanted_item(item_stack, new HashMap(), only_if_enchanted); - } - - public ItemStack update_enchanted_item( - ItemStack item_stack, - Map additional_enchantments, - boolean only_if_enchanted - ) { - remove_old_lore(item_stack); - return item_stack; - } - - private void remove_superseded(ItemStack item_stack, Map enchantments) { - // if (enchantments.isEmpty()) { - // return; - // } - - // 1. Build a list of all enchantments that would be removed because - // they are superseded by some enchantment. - // final var to_remove_inclusive = enchantments.keySet().stream() - // .map(x -> ((CraftEnchantment)x).getHandle()) - // .filter(x -> x instanceof NativeEnchantmentWrapper) - // .map(x -> ((NativeEnchantmentWrapper)x).custom().supersedes()) - // .flatMap(Set::stream) - // .collect(Collectors.toSet()); - - // 2. Before removing these enchantments, first re-build the list but - // ignore any enchantments in the calculation that would themselves - // be removed. This prevents them from contributing to the list of - // enchantments to remove. Consider this: A supersedes B, and B supersedes C, but - // A doesn't supersede C. Now an item with A B and C should get reduced to - // A and C, not just to A. - // var to_remove = enchantments.keySet().stream() - // .map(x -> ((CraftEnchantment)x).getHandle()) - // .filter(x -> x instanceof NativeEnchantmentWrapper) - // .filter(x -> !to_remove_inclusive.contains(((NativeEnchantmentWrapper)x).custom().key())) // Ignore enchantments that are themselves removed. - // .map(x -> ((NativeEnchantmentWrapper)x).custom().supersedes()) - // .flatMap(Set::stream) - // .map(x -> org.bukkit.Registry.ENCHANTMENT.get(x)) - // .collect(Collectors.toSet()); - - // for (var e : to_remove) { - // item_stack.removeEnchantment(e); - // enchantments.remove(e); - // } - } - - private void remove_old_lore(ItemStack item_stack) { - var lore = item_stack.lore(); - if (lore == null) { - lore = new ArrayList(); - } - - lore.removeIf(this::is_enchantment_lore); - - // Set lore - item_stack.lore(lore.isEmpty() ? null : lore); - } - - private boolean is_enchantment_lore(final Component component) { - // FIXME legacy If the component begins with a translated lore from vane enchantments, it is always from us. (needed for backward compatibility) - if (component instanceof TranslatableComponent translatable_component && translatable_component.key().startsWith("vane_enchantments.")) { - return true; - } - - return ItemUtil.has_sentinel(component, SENTINEL); - } - - // Triggers on Anvils, grindstones, and smithing tables. - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void on_prepare_enchanted_edit(final PrepareResultEvent event) { - if (event.getResult() == null) { - return; - } - - event.setResult(update_enchanted_item(event.getResult().clone())); - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void on_enchant_item(final EnchantItemEvent event) { - final var map = new HashMap(event.getEnchantsToAdd()); - update_enchanted_item(event.getItem(), map); - } - - // @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - // public void on_loot_generate(final LootGenerateEvent event) { - // for (final var item : event.getLoot()) { - // // Update all item lore in case they are enchanted - // update_enchanted_item(item, true); - // } - // } - - private MerchantRecipe process_recipe(final MerchantRecipe recipe) { - var result = recipe.getResult().clone(); - - // Create a new recipe - final var new_recipe = new MerchantRecipe( - update_enchanted_item(result, true), - recipe.getUses(), - recipe.getMaxUses(), - recipe.hasExperienceReward(), - recipe.getVillagerExperience(), - recipe.getPriceMultiplier() - ); - recipe.getIngredients().forEach(i -> new_recipe.addIngredient(i)); - return new_recipe; - } - - // @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - // public void on_acquire_trade(final VillagerAcquireTradeEvent event) { - // event.setRecipe(process_recipe(event.getRecipe())); - // } - - // @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - // public void on_right_click_villager(final PlayerInteractEntityEvent event) { - // final var entity = event.getRightClicked(); - // if (!(entity instanceof Merchant)) { - // return; - // } - - // final var merchant = (Merchant) entity; - // final var recipes = new ArrayList(); - - // // Check all recipes - // for (final var r : merchant.getRecipes()) { - // recipes.add(process_recipe(r)); - // } - - // // Update recipes - // merchant.setRecipes(recipes); - // } + + private static final NamespacedKey SENTINEL = StorageUtil.namespaced_key("vane", "enchantment_lore"); + + public EnchantmentManager(Context context) { + super(context); + } + + public ItemStack update_enchanted_item(ItemStack item_stack) { + return update_enchanted_item(item_stack, new HashMap(), false); + } + + public ItemStack update_enchanted_item(ItemStack item_stack, Map additional_enchantments) { + return update_enchanted_item(item_stack, additional_enchantments, false); + } + + public ItemStack update_enchanted_item(ItemStack item_stack, boolean only_if_enchanted) { + return update_enchanted_item(item_stack, new HashMap(), only_if_enchanted); + } + + public ItemStack update_enchanted_item( + ItemStack item_stack, + Map additional_enchantments, + boolean only_if_enchanted + ) { + remove_old_lore(item_stack); + return item_stack; + } + + private void remove_superseded(ItemStack item_stack, Map enchantments) { + // if (enchantments.isEmpty()) { + // return; + // } + + // 1. Build a list of all enchantments that would be removed because + // they are superseded by some enchantment. + // final var to_remove_inclusive = enchantments.keySet().stream() + // .map(x -> ((CraftEnchantment)x).getHandle()) + // .filter(x -> x instanceof NativeEnchantmentWrapper) + // .map(x -> ((NativeEnchantmentWrapper)x).custom().supersedes()) + // .flatMap(Set::stream) + // .collect(Collectors.toSet()); + + // 2. Before removing these enchantments, first re-build the list but + // ignore any enchantments in the calculation that would themselves + // be removed. This prevents them from contributing to the list of + // enchantments to remove. Consider this: A supersedes B, and B supersedes C, but + // A doesn't supersede C. Now an item with A B and C should get reduced to + // A and C, not just to A. + // var to_remove = enchantments.keySet().stream() + // .map(x -> ((CraftEnchantment)x).getHandle()) + // .filter(x -> x instanceof NativeEnchantmentWrapper) + // .filter(x -> + // !to_remove_inclusive.contains(((NativeEnchantmentWrapper)x).custom().key())) // Ignore + // enchantments that are themselves removed. + // .map(x -> ((NativeEnchantmentWrapper)x).custom().supersedes()) + // .flatMap(Set::stream) + // .map(x -> org.bukkit.Registry.ENCHANTMENT.get(x)) + // .collect(Collectors.toSet()); + + // for (var e : to_remove) { + // item_stack.removeEnchantment(e); + // enchantments.remove(e); + // } + } + + private void remove_old_lore(ItemStack item_stack) { + var lore = item_stack.lore(); + if (lore == null) { + lore = new ArrayList(); + } + + lore.removeIf(this::is_enchantment_lore); + + // Set lore + item_stack.lore(lore.isEmpty() ? null : lore); + } + + private boolean is_enchantment_lore(final Component component) { + // FIXME legacy If the component begins with a translated lore from vane enchantments, it is + // always from us. (needed for backward compatibility) + if ( + component instanceof TranslatableComponent translatable_component && + translatable_component.key().startsWith("vane_enchantments.") + ) { + return true; + } + + return ItemUtil.has_sentinel(component, SENTINEL); + } + + // Triggers on Anvils, grindstones, and smithing tables. + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on_prepare_enchanted_edit(final PrepareResultEvent event) { + if (event.getResult() == null) { + return; + } + + event.setResult(update_enchanted_item(event.getResult().clone())); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on_enchant_item(final EnchantItemEvent event) { + final var map = new HashMap(event.getEnchantsToAdd()); + update_enchanted_item(event.getItem(), map); + } + + // @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + // public void on_loot_generate(final LootGenerateEvent event) { + // for (final var item : event.getLoot()) { + // // Update all item lore in case they are enchanted + // update_enchanted_item(item, true); + // } + // } + + private MerchantRecipe process_recipe(final MerchantRecipe recipe) { + var result = recipe.getResult().clone(); + + // Create a new recipe + final var new_recipe = new MerchantRecipe( + update_enchanted_item(result, true), + recipe.getUses(), + recipe.getMaxUses(), + recipe.hasExperienceReward(), + recipe.getVillagerExperience(), + recipe.getPriceMultiplier() + ); + recipe.getIngredients().forEach(i -> new_recipe.addIngredient(i)); + return new_recipe; + } + // @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + // public void on_acquire_trade(final VillagerAcquireTradeEvent event) { + // event.setRecipe(process_recipe(event.getRecipe())); + // } + + // @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + // public void on_right_click_villager(final PlayerInteractEntityEvent event) { + // final var entity = event.getRightClicked(); + // if (!(entity instanceof Merchant)) { + // return; + // } + + // final var merchant = (Merchant) entity; + // final var recipes = new ArrayList(); + + // // Check all recipes + // for (final var r : merchant.getRecipes()) { + // recipes.add(process_recipe(r)); + // } + + // // Update recipes + // merchant.setRecipes(recipes); + // } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer1.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer1.java index 7913b7b13..7b7a081da 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer1.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer1.java @@ -4,15 +4,15 @@ @FunctionalInterface public interface Consumer1 extends ErasedFunctor, GenericsFinder { - void apply(T1 t1); + void apply(T1 t1); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 1) { - throw new IllegalArgumentException("Functor needs 1 arguments but got " + args.size() + " arguments"); - } - apply((T1) args.get(0)); - return null; - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 1) { + throw new IllegalArgumentException("Functor needs 1 arguments but got " + args.size() + " arguments"); + } + apply((T1) args.get(0)); + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer2.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer2.java index 98c652a1b..52b0d2241 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer2.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer2.java @@ -4,15 +4,15 @@ @FunctionalInterface public interface Consumer2 extends ErasedFunctor, GenericsFinder { - void apply(T1 t1, T2 t2); + void apply(T1 t1, T2 t2); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 2) { - throw new IllegalArgumentException("Functor needs 2 arguments but got " + args.size() + " arguments"); - } - apply((T1) args.get(0), (T2) args.get(1)); - return null; - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 2) { + throw new IllegalArgumentException("Functor needs 2 arguments but got " + args.size() + " arguments"); + } + apply((T1) args.get(0), (T2) args.get(1)); + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer3.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer3.java index 8f88ef9ea..97763cb18 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer3.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer3.java @@ -4,15 +4,15 @@ @FunctionalInterface public interface Consumer3 extends ErasedFunctor, GenericsFinder { - void apply(T1 t1, T2 t2, T3 t3); + void apply(T1 t1, T2 t2, T3 t3); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 3) { - throw new IllegalArgumentException("Functor needs 3 arguments but got " + args.size() + " arguments"); - } - apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2)); - return null; - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 3) { + throw new IllegalArgumentException("Functor needs 3 arguments but got " + args.size() + " arguments"); + } + apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2)); + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer4.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer4.java index f96bb206c..3a25b8d9e 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer4.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer4.java @@ -4,15 +4,15 @@ @FunctionalInterface public interface Consumer4 extends ErasedFunctor, GenericsFinder { - void apply(T1 t1, T2 t2, T3 t3, T4 t4); + void apply(T1 t1, T2 t2, T3 t3, T4 t4); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 4) { - throw new IllegalArgumentException("Functor needs 4 arguments but got " + args.size() + " arguments"); - } - apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3)); - return null; - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 4) { + throw new IllegalArgumentException("Functor needs 4 arguments but got " + args.size() + " arguments"); + } + apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3)); + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer5.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer5.java index bc87ff28b..50e313499 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer5.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer5.java @@ -4,15 +4,15 @@ @FunctionalInterface public interface Consumer5 extends ErasedFunctor, GenericsFinder { - void apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); + void apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 5) { - throw new IllegalArgumentException("Functor needs 5 arguments but got " + args.size() + " arguments"); - } - apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3), (T5) args.get(4)); - return null; - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 5) { + throw new IllegalArgumentException("Functor needs 5 arguments but got " + args.size() + " arguments"); + } + apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3), (T5) args.get(4)); + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer6.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer6.java index 72cd23bc5..5e7adc9d7 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer6.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Consumer6.java @@ -4,22 +4,22 @@ @FunctionalInterface public interface Consumer6 extends ErasedFunctor, GenericsFinder { - void apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); + void apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 6) { - throw new IllegalArgumentException("Functor needs 6 arguments but got " + args.size() + " arguments"); - } - apply( - (T1) args.get(0), - (T2) args.get(1), - (T3) args.get(2), - (T4) args.get(3), - (T5) args.get(4), - (T6) args.get(5) - ); - return null; - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 6) { + throw new IllegalArgumentException("Functor needs 6 arguments but got " + args.size() + " arguments"); + } + apply( + (T1) args.get(0), + (T2) args.get(1), + (T3) args.get(2), + (T4) args.get(3), + (T5) args.get(4), + (T6) args.get(5) + ); + return null; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/ErasedFunctor.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/ErasedFunctor.java index cee6a87c1..ef0902ba7 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/ErasedFunctor.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/ErasedFunctor.java @@ -3,5 +3,5 @@ import java.util.List; public interface ErasedFunctor { - public Object invoke(List args); + public Object invoke(List args); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function1.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function1.java index 14e010d99..da6b1a4ff 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function1.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function1.java @@ -4,14 +4,14 @@ @FunctionalInterface public interface Function1 extends ErasedFunctor, GenericsFinder { - R apply(T1 t1); + R apply(T1 t1); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 1) { - throw new IllegalArgumentException("Functor needs 1 arguments but got " + args.size() + " arguments"); - } - return apply((T1) args.get(0)); - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 1) { + throw new IllegalArgumentException("Functor needs 1 arguments but got " + args.size() + " arguments"); + } + return apply((T1) args.get(0)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function2.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function2.java index 4e5464d48..d3e1b36a7 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function2.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function2.java @@ -4,14 +4,14 @@ @FunctionalInterface public interface Function2 extends ErasedFunctor, GenericsFinder { - R apply(T1 t1, T2 t2); + R apply(T1 t1, T2 t2); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 2) { - throw new IllegalArgumentException("Functor needs 2 arguments but got " + args.size() + " arguments"); - } - return apply((T1) args.get(0), (T2) args.get(1)); - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 2) { + throw new IllegalArgumentException("Functor needs 2 arguments but got " + args.size() + " arguments"); + } + return apply((T1) args.get(0), (T2) args.get(1)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function3.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function3.java index 8e623dbc9..a300f565b 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function3.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function3.java @@ -4,14 +4,14 @@ @FunctionalInterface public interface Function3 extends ErasedFunctor, GenericsFinder { - R apply(T1 t1, T2 t2, T3 t3); + R apply(T1 t1, T2 t2, T3 t3); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 3) { - throw new IllegalArgumentException("Functor needs 3 arguments but got " + args.size() + " arguments"); - } - return apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2)); - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 3) { + throw new IllegalArgumentException("Functor needs 3 arguments but got " + args.size() + " arguments"); + } + return apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function4.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function4.java index 61665645e..690a5b8aa 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function4.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function4.java @@ -4,14 +4,14 @@ @FunctionalInterface public interface Function4 extends ErasedFunctor, GenericsFinder { - R apply(T1 t1, T2 t2, T3 t3, T4 t4); + R apply(T1 t1, T2 t2, T3 t3, T4 t4); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 4) { - throw new IllegalArgumentException("Functor needs 4 arguments but got " + args.size() + " arguments"); - } - return apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3)); - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 4) { + throw new IllegalArgumentException("Functor needs 4 arguments but got " + args.size() + " arguments"); + } + return apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function5.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function5.java index 044aaa4ce..60973d496 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function5.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function5.java @@ -4,14 +4,14 @@ @FunctionalInterface public interface Function5 extends ErasedFunctor, GenericsFinder { - R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 5) { - throw new IllegalArgumentException("Functor needs 5 arguments but got " + args.size() + " arguments"); - } - return apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3), (T5) args.get(4)); - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 5) { + throw new IllegalArgumentException("Functor needs 5 arguments but got " + args.size() + " arguments"); + } + return apply((T1) args.get(0), (T2) args.get(1), (T3) args.get(2), (T4) args.get(3), (T5) args.get(4)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function6.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function6.java index 304da4bc8..07ba026a3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/Function6.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/Function6.java @@ -4,21 +4,21 @@ @FunctionalInterface public interface Function6 extends ErasedFunctor, GenericsFinder { - R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); - @Override - @SuppressWarnings("unchecked") - public default Object invoke(List args) { - if (args.size() != 6) { - throw new IllegalArgumentException("Functor needs 6 arguments but got " + args.size() + " arguments"); - } - return apply( - (T1) args.get(0), - (T2) args.get(1), - (T3) args.get(2), - (T4) args.get(3), - (T5) args.get(4), - (T6) args.get(5) - ); - } + @Override + @SuppressWarnings("unchecked") + public default Object invoke(List args) { + if (args.size() != 6) { + throw new IllegalArgumentException("Functor needs 6 arguments but got " + args.size() + " arguments"); + } + return apply( + (T1) args.get(0), + (T2) args.get(1), + (T3) args.get(2), + (T4) args.get(3), + (T5) args.get(4), + (T6) args.get(5) + ); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/functional/GenericsFinder.java b/vane-core/src/main/java/org/oddlama/vane/core/functional/GenericsFinder.java index 5139430c5..d020dea6b 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/functional/GenericsFinder.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/functional/GenericsFinder.java @@ -7,31 +7,31 @@ import java.util.Objects; public interface GenericsFinder extends Serializable { - default SerializedLambda serialized() { - try { - Method replaceMethod = getClass().getDeclaredMethod("writeReplace"); - replaceMethod.setAccessible(true); - return (SerializedLambda) replaceMethod.invoke(this); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + default SerializedLambda serialized() { + try { + Method replaceMethod = getClass().getDeclaredMethod("writeReplace"); + replaceMethod.setAccessible(true); + return (SerializedLambda) replaceMethod.invoke(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - default Class getContainingClass() { - try { - String className = serialized().getImplClass().replaceAll("/", "."); - return Class.forName(className); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + default Class getContainingClass() { + try { + String className = serialized().getImplClass().replaceAll("/", "."); + return Class.forName(className); + } catch (Exception e) { + throw new RuntimeException(e); + } + } - default Method method() { - SerializedLambda lambda = serialized(); - Class containingClass = getContainingClass(); - return Arrays.stream(containingClass.getDeclaredMethods()) - .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName())) - .findFirst() - .orElseThrow(RuntimeException::new); - } + default Method method() { + SerializedLambda lambda = serialized(); + Class containingClass = getContainingClass(); + return Arrays.stream(containingClass.getDeclaredMethods()) + .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName())) + .findFirst() + .orElseThrow(RuntimeException::new); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItem.java b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItem.java index 1d7969a22..a14935c64 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItem.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItem.java @@ -1,7 +1,9 @@ package org.oddlama.vane.core.item; import java.io.IOException; - +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.oddlama.vane.annotation.config.ConfigInt; @@ -18,130 +20,131 @@ import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.util.StorageUtil; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextDecoration; - public class CustomItem> extends Listener implements org.oddlama.vane.core.item.api.CustomItem { - private VaneItem annotation; - public NamespacedKey key; - - public Recipes recipes; - public LootTables loot_tables; - - // Language - @LangMessage - public TranslatedMessage lang_name; - - @ConfigInt(def = 0, min = 0, desc = "The durability of this item. Set to 0 to use the durability properties of whatever base material the item is made of.") - private int config_durability; - private final String name_override; - private final Integer custom_model_data_override; - - public CustomItem(Context context) { - this(context, null, null); - } - - public CustomItem(Context context, String name_override, Integer custom_model_data_override) { - super(null); - - Class cls = getClass(); - while (this.annotation == null && cls != null) { - this.annotation = cls.getAnnotation(VaneItem.class); - cls = cls.getSuperclass(); - } - if (this.annotation == null) { - throw new IllegalStateException("Could not find @VaneItem annotation on " + getClass()); - } - - this.name_override = name_override; - this.custom_model_data_override = custom_model_data_override; - - // Set namespace delayed, as we need to access instance methods to do so. - context = context.group("item_" + name(), "Enable item " + name()); - set_context(context); - - this.key = StorageUtil.namespaced_key(get_module().namespace(), name()); - recipes = new Recipes(get_context(), this.key, this::default_recipes); - loot_tables = new LootTables(get_context(), this.key, this::default_loot_tables); - - // Register item - get_module().core.item_registry().register(this); - } - - @Override - public NamespacedKey key() { - return key; - } - - public String name() { - if (name_override != null) { - return name_override; - } - return annotation.name(); - } - - @Override - public boolean enabled() { - // Explicitly stated to not be forgotten, as enabled() is also part of Listener. - return annotation.enabled() && super.enabled(); - } - - @Override - public int version() { - return annotation.version(); - } - - @Override - public Material baseMaterial() { - return annotation.base(); - } - - @Override - public int customModelData() { - if (custom_model_data_override != null) { - return custom_model_data_override; - } - return annotation.model_data(); - } - - @Override - public Component displayName() { - return lang_name.format().decoration(TextDecoration.ITALIC, false); - } - - public int config_durability_def() { - return annotation.durability(); - } - - @Override - public int durability() { - return config_durability; - } - - public RecipeList default_recipes() { - return RecipeList.of(); - } - - public LootTableList default_loot_tables() { - return LootTableList.of(); - } - - /** - * Returns the type of item for the resource pack - */ - public Key itemType(){ - return Key.key(Key.MINECRAFT_NAMESPACE, "item/generated"); - } - - @Override - public void addResources(final ResourcePackGenerator rp) throws IOException { - final var resource_name = "items/" + key().value() + ".png"; - final var resource = get_module().getResource(resource_name); - if (resource == null) { - throw new RuntimeException("Missing resource '" + resource_name + "'. This is a bug."); - } - rp.add_item_model(key(), resource, itemType()); - rp.add_item_override(baseMaterial().getKey(), key(), predicate -> predicate.put("custom_model_data", customModelData())); - } + + private VaneItem annotation; + public NamespacedKey key; + + public Recipes recipes; + public LootTables loot_tables; + + // Language + @LangMessage + public TranslatedMessage lang_name; + + @ConfigInt( + def = 0, + min = 0, + desc = "The durability of this item. Set to 0 to use the durability properties of whatever base material the item is made of." + ) + private int config_durability; + + private final String name_override; + private final Integer custom_model_data_override; + + public CustomItem(Context context) { + this(context, null, null); + } + + public CustomItem(Context context, String name_override, Integer custom_model_data_override) { + super(null); + Class cls = getClass(); + while (this.annotation == null && cls != null) { + this.annotation = cls.getAnnotation(VaneItem.class); + cls = cls.getSuperclass(); + } + if (this.annotation == null) { + throw new IllegalStateException("Could not find @VaneItem annotation on " + getClass()); + } + + this.name_override = name_override; + this.custom_model_data_override = custom_model_data_override; + + // Set namespace delayed, as we need to access instance methods to do so. + context = context.group("item_" + name(), "Enable item " + name()); + set_context(context); + + this.key = StorageUtil.namespaced_key(get_module().namespace(), name()); + recipes = new Recipes(get_context(), this.key, this::default_recipes); + loot_tables = new LootTables(get_context(), this.key, this::default_loot_tables); + + // Register item + get_module().core.item_registry().register(this); + } + + @Override + public NamespacedKey key() { + return key; + } + + public String name() { + if (name_override != null) { + return name_override; + } + return annotation.name(); + } + + @Override + public boolean enabled() { + // Explicitly stated to not be forgotten, as enabled() is also part of Listener. + return annotation.enabled() && super.enabled(); + } + + @Override + public int version() { + return annotation.version(); + } + + @Override + public Material baseMaterial() { + return annotation.base(); + } + + @Override + public int customModelData() { + if (custom_model_data_override != null) { + return custom_model_data_override; + } + return annotation.model_data(); + } + + @Override + public Component displayName() { + return lang_name.format().decoration(TextDecoration.ITALIC, false); + } + + public int config_durability_def() { + return annotation.durability(); + } + + @Override + public int durability() { + return config_durability; + } + + public RecipeList default_recipes() { + return RecipeList.of(); + } + + public LootTableList default_loot_tables() { + return LootTableList.of(); + } + + /** Returns the type of item for the resource pack */ + public Key itemType() { + return Key.key(Key.MINECRAFT_NAMESPACE, "item/generated"); + } + + @Override + public void addResources(final ResourcePackGenerator rp) throws IOException { + final var resource_name = "items/" + key().value() + ".png"; + final var resource = get_module().getResource(resource_name); + if (resource == null) { + throw new RuntimeException("Missing resource '" + resource_name + "'. This is a bug."); + } + rp.add_item_model(key(), resource, itemType()); + rp.add_item_override(baseMaterial().getKey(), key(), predicate -> + predicate.put("custom_model_data", customModelData()) + ); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java index 1a86aa111..cf6411f13 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemHelper.java @@ -11,89 +11,89 @@ import org.oddlama.vane.util.StorageUtil; public class CustomItemHelper { - /** Used in persistent item storage to identify custom items. */ - public static final NamespacedKey CUSTOM_ITEM_IDENTIFIER = StorageUtil.namespaced_key("vane", "custom_item_identifier"); - /** Used in persistent item storage to store a custom item version. */ - public static final NamespacedKey CUSTOM_ITEM_VERSION = StorageUtil.namespaced_key("vane", "custom_item_version"); - /** - * Internal function. Used as a dispatcher to update internal information and then call - * {@link #updateItemStack(ItemStack)} to let the user update information. This prevents - * problems with information de-sync in case the user would forget to call super. - */ - public static ItemStack updateItemStack(final CustomItem customItem, @NotNull final ItemStack itemStack) { - itemStack.editMeta(meta -> { - final var data = meta.getPersistentDataContainer(); - data.set(CUSTOM_ITEM_IDENTIFIER, PersistentDataType.STRING, customItem.key().toString()); - data.set(CUSTOM_ITEM_VERSION, PersistentDataType.INTEGER, customItem.version()); - meta.setCustomModelData(customItem.customModelData()); - }); + /** Used in persistent item storage to identify custom items. */ + public static final NamespacedKey CUSTOM_ITEM_IDENTIFIER = StorageUtil.namespaced_key( + "vane", + "custom_item_identifier" + ); - DurabilityManager.initialize_or_update_max(customItem, itemStack); - return customItem.updateItemStack(itemStack); - } + /** Used in persistent item storage to store a custom item version. */ + public static final NamespacedKey CUSTOM_ITEM_VERSION = StorageUtil.namespaced_key("vane", "custom_item_version"); - /** - * Returns the resourceKey key and version number of the stored custom item - * tag on the given item, if any. Returns null if none was found or the given item stack was null. - */ - public static Pair customItemTagsFromItemStack(@Nullable final ItemStack itemStack) { - if (itemStack == null || !itemStack.hasItemMeta()) { - return null; - } + /** + * Internal function. Used as a dispatcher to update internal information and then call {@link + * #updateItemStack(ItemStack)} to let the user update information. This prevents problems with + * information de-sync in case the user would forget to call super. + */ + public static ItemStack updateItemStack(final CustomItem customItem, @NotNull final ItemStack itemStack) { + itemStack.editMeta(meta -> { + final var data = meta.getPersistentDataContainer(); + data.set(CUSTOM_ITEM_IDENTIFIER, PersistentDataType.STRING, customItem.key().toString()); + data.set(CUSTOM_ITEM_VERSION, PersistentDataType.INTEGER, customItem.version()); + meta.setCustomModelData(customItem.customModelData()); + }); - final var data = itemStack.getItemMeta().getPersistentDataContainer(); - final var key = data.get(CUSTOM_ITEM_IDENTIFIER, PersistentDataType.STRING); - final var version = data.get(CUSTOM_ITEM_VERSION, PersistentDataType.INTEGER); - if (key == null || version == null) { - return null; - } + DurabilityManager.initialize_or_update_max(customItem, itemStack); + return customItem.updateItemStack(itemStack); + } - final var parts = key.split(":"); - if (parts.length != 2) { - throw new IllegalStateException("Invalid namespaced key '" + key + "'"); - } - return Pair.of(StorageUtil.namespaced_key(parts[0], parts[1]), version); - } + /** + * Returns the resourceKey key and version number of the stored custom item tag on the given + * item, if any. Returns null if none was found or the given item stack was null. + */ + public static Pair customItemTagsFromItemStack(@Nullable final ItemStack itemStack) { + if (itemStack == null || !itemStack.hasItemMeta()) { + return null; + } - /** - * Creates a new item stack with a single item of this custom item. - */ - public static ItemStack newStack(final String custom_item_key) { - return CustomItemHelper.newStack(custom_item_key, 1); - } + final var data = itemStack.getItemMeta().getPersistentDataContainer(); + final var key = data.get(CUSTOM_ITEM_IDENTIFIER, PersistentDataType.STRING); + final var version = data.get(CUSTOM_ITEM_VERSION, PersistentDataType.INTEGER); + if (key == null || version == null) { + return null; + } - /** - * Creates a new item stack with the given number of items of this custom item. - */ - public static ItemStack newStack(final String custom_item_key, final int amount) { - return CustomItemHelper.newStack(Core.instance().item_registry().get(NamespacedKey.fromString(custom_item_key)), amount); - } + final var parts = key.split(":"); + if (parts.length != 2) { + throw new IllegalStateException("Invalid namespaced key '" + key + "'"); + } + return Pair.of(StorageUtil.namespaced_key(parts[0], parts[1]), version); + } - /** - * Creates a new item stack with a single item of this custom item. - */ - public static ItemStack newStack(final CustomItem customItem) { - return CustomItemHelper.newStack(customItem, 1); - } + /** Creates a new item stack with a single item of this custom item. */ + public static ItemStack newStack(final String custom_item_key) { + return CustomItemHelper.newStack(custom_item_key, 1); + } - /** - * Creates a new item stack with the given number of items of this custom item. - */ - public static ItemStack newStack(final CustomItem customItem, final int amount) { - final var itemStack = new ItemStack(customItem.baseMaterial(), amount); - itemStack.editMeta(meta -> meta.itemName(customItem.displayName())); - return CustomItemHelper.updateItemStack(customItem, itemStack); - } + /** Creates a new item stack with the given number of items of this custom item. */ + public static ItemStack newStack(final String custom_item_key, final int amount) { + return CustomItemHelper.newStack( + Core.instance().item_registry().get(NamespacedKey.fromString(custom_item_key)), + amount + ); + } - /** - * This function is called to convert an existing item stack of any - * form to this custom item type, without losing metadata such as name, enchantments, etc. - * This is for example useful to convert a diamond something into a netherite something, - * when those two items are different CustomItem definitions but otherwise share attributes and functionality. - */ - public static ItemStack convertExistingStack(final CustomItem customItem, ItemStack itemStack) { - itemStack = itemStack.clone().withType(customItem.baseMaterial()); - return CustomItemHelper.updateItemStack(customItem, itemStack); - } + /** Creates a new item stack with a single item of this custom item. */ + public static ItemStack newStack(final CustomItem customItem) { + return CustomItemHelper.newStack(customItem, 1); + } + + /** Creates a new item stack with the given number of items of this custom item. */ + public static ItemStack newStack(final CustomItem customItem, final int amount) { + final var itemStack = new ItemStack(customItem.baseMaterial(), amount); + itemStack.editMeta(meta -> meta.itemName(customItem.displayName())); + return CustomItemHelper.updateItemStack(customItem, itemStack); + } + + /** + * This function is called to convert an existing item stack of any form to this custom item + * type, without losing metadata such as name, enchantments, etc. This is for example useful to + * convert a diamond something into a netherite something, when those two items are different + * CustomItem definitions but otherwise share attributes and functionality. + */ + public static ItemStack convertExistingStack(final CustomItem customItem, ItemStack itemStack) { + itemStack = itemStack.clone().withType(customItem.baseMaterial()); + return CustomItemHelper.updateItemStack(customItem, itemStack); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemRegistry.java b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemRegistry.java index fa1c95818..a36032074 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemRegistry.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomItemRegistry.java @@ -3,7 +3,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; - import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; @@ -12,60 +11,63 @@ import org.oddlama.vane.core.item.api.CustomModelDataRegistry; public class CustomItemRegistry implements org.oddlama.vane.core.item.api.CustomItemRegistry { - private final HashMap items = new HashMap<>(); - private final HashSet items_to_remove = new HashSet<>(); - private CustomModelDataRegistry model_data_registry; - public CustomItemRegistry() { - this.model_data_registry = Core.instance().model_data_registry(); - } + private final HashMap items = new HashMap<>(); + private final HashSet items_to_remove = new HashSet<>(); + private CustomModelDataRegistry model_data_registry; + + public CustomItemRegistry() { + this.model_data_registry = Core.instance().model_data_registry(); + } - @Override - public @Nullable boolean has(final NamespacedKey resourceKey) { - return items.containsKey(resourceKey); - } + @Override + public @Nullable boolean has(final NamespacedKey resourceKey) { + return items.containsKey(resourceKey); + } - @Override - public @Nullable Collection all() { - return items.values(); - } + @Override + public @Nullable Collection all() { + return items.values(); + } - @Override - public @Nullable CustomItem get(final NamespacedKey resourceKey) { - return items.get(resourceKey); - } + @Override + public @Nullable CustomItem get(final NamespacedKey resourceKey) { + return items.get(resourceKey); + } - @Override - public @Nullable CustomItem get(@Nullable final ItemStack itemStack) { - final var key_and_version = CustomItemHelper.customItemTagsFromItemStack(itemStack); - if (key_and_version == null) { - return null; - } + @Override + public @Nullable CustomItem get(@Nullable final ItemStack itemStack) { + final var key_and_version = CustomItemHelper.customItemTagsFromItemStack(itemStack); + if (key_and_version == null) { + return null; + } - return get(key_and_version.getLeft()); - } + return get(key_and_version.getLeft()); + } - @Override - public void register(final CustomItem customItem) { - model_data_registry.reserveSingle(customItem.key(), customItem.customModelData()); - if (has(customItem.key())) { - throw new IllegalArgumentException("A custom item with the same key '" + customItem.key() + "' has already been registered"); - } - items.put(customItem.key(), customItem); - } + @Override + public void register(final CustomItem customItem) { + model_data_registry.reserveSingle(customItem.key(), customItem.customModelData()); + if (has(customItem.key())) { + throw new IllegalArgumentException( + "A custom item with the same key '" + customItem.key() + "' has already been registered" + ); + } + items.put(customItem.key(), customItem); + } - @Override - public void removePermanently(final NamespacedKey key) { - items_to_remove.add(key); - } + @Override + public void removePermanently(final NamespacedKey key) { + items_to_remove.add(key); + } - @Override - public boolean shouldRemove(NamespacedKey key) { - return items_to_remove.contains(key); - } + @Override + public boolean shouldRemove(NamespacedKey key) { + return items_to_remove.contains(key); + } - @Override - public CustomModelDataRegistry dataRegistry() { - return model_data_registry; - } + @Override + public CustomModelDataRegistry dataRegistry() { + return model_data_registry; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomModelDataRegistry.java b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomModelDataRegistry.java index b29b4153e..1c8de73f4 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/CustomModelDataRegistry.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/CustomModelDataRegistry.java @@ -1,65 +1,67 @@ package org.oddlama.vane.core.item; import java.util.HashMap; - import org.bukkit.NamespacedKey; import org.jetbrains.annotations.Nullable; public class CustomModelDataRegistry implements org.oddlama.vane.core.item.api.CustomModelDataRegistry { - private final HashMap reserved_ranges = new HashMap<>(); - @Override - public boolean has(int data) { - return reserved_ranges.values().stream().anyMatch(r -> r.contains(data)); - } + private final HashMap reserved_ranges = new HashMap<>(); + + @Override + public boolean has(int data) { + return reserved_ranges.values().stream().anyMatch(r -> r.contains(data)); + } - @Override - public boolean hasAny(Range range) { - return reserved_ranges.values().stream().anyMatch(r -> r.overlaps(range)); - } + @Override + public boolean hasAny(Range range) { + return reserved_ranges.values().stream().anyMatch(r -> r.overlaps(range)); + } - @Override - public @Nullable Range get(NamespacedKey resourceKey) { - return reserved_ranges.get(resourceKey); - } + @Override + public @Nullable Range get(NamespacedKey resourceKey) { + return reserved_ranges.get(resourceKey); + } - @Override - public @Nullable NamespacedKey get(int data) { - for (final var kv : reserved_ranges.entrySet()) { - if (kv.getValue().contains(data)) { - return kv.getKey(); - } - } + @Override + public @Nullable NamespacedKey get(int data) { + for (final var kv : reserved_ranges.entrySet()) { + if (kv.getValue().contains(data)) { + return kv.getKey(); + } + } - return null; - } + return null; + } - @Override - public @Nullable NamespacedKey get(Range range) { - for (final var kv : reserved_ranges.entrySet()) { - if (kv.getValue().overlaps(range)) { - return kv.getKey(); - } - } + @Override + public @Nullable NamespacedKey get(Range range) { + for (final var kv : reserved_ranges.entrySet()) { + if (kv.getValue().overlaps(range)) { + return kv.getKey(); + } + } - return null; - } + return null; + } - @Override - public void reserve(NamespacedKey resourceKey, Range range) { - final var existing = get(range); - if (existing != null) { - throw new IllegalArgumentException("Cannot reserve range " + range + ", already registered by " + existing); - } - reserved_ranges.put(resourceKey, range); - } + @Override + public void reserve(NamespacedKey resourceKey, Range range) { + final var existing = get(range); + if (existing != null) { + throw new IllegalArgumentException("Cannot reserve range " + range + ", already registered by " + existing); + } + reserved_ranges.put(resourceKey, range); + } - @Override - public void reserveSingle(NamespacedKey resourceKey, int data) { - final var existing = get(data); - if (existing != null) { - throw new IllegalArgumentException("Cannot reserve customModelData " + data + ", already registered by " + existing); - } - reserved_ranges.put(resourceKey, new Range(data, data + 1)); - } + @Override + public void reserveSingle(NamespacedKey resourceKey, int data) { + final var existing = get(data); + if (existing != null) { + throw new IllegalArgumentException( + "Cannot reserve customModelData " + data + ", already registered by " + existing + ); + } + reserved_ranges.put(resourceKey, new Range(data, data + 1)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java b/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java index d83d48aab..12d58c034 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/DurabilityManager.java @@ -1,5 +1,6 @@ package org.oddlama.vane.core.item; +import net.kyori.adventure.text.Component; import org.bukkit.NamespacedKey; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -15,172 +16,168 @@ import org.oddlama.vane.util.ItemUtil; import org.oddlama.vane.util.StorageUtil; -import net.kyori.adventure.text.Component; - // TODO: what about inventory based item repair? public class DurabilityManager extends Listener { - public static final NamespacedKey ITEM_DURABILITY_MAX = StorageUtil.namespaced_key("vane", "durability.max"); - public static final NamespacedKey ITEM_DURABILITY_DAMAGE = StorageUtil.namespaced_key("vane", "durability.damage"); - - private static final NamespacedKey SENTINEL = StorageUtil.namespaced_key("vane", "durability_override_lore"); - - public DurabilityManager(final Context context) { - super(context); - } - - /** - * Returns true if the given component is associated to our custom durability. - */ - private static boolean is_durability_lore(final Component component) { - return ItemUtil.has_sentinel(component, SENTINEL); - } - - /** - * Removes associated lore from an item. - */ - private static void remove_lore(final ItemStack item_stack) { - final var lore = item_stack.lore(); - if (lore != null) { - lore.removeIf(DurabilityManager::is_durability_lore); - if (lore.size() > 0) { - item_stack.lore(lore); - } else { - item_stack.lore(null); - } - } - } - - /** - * Sets the item's damage regarding our custom durability. The durability will - * get - * clamped to plausible values. Damage values >= max will result in item - * breakage. - * The maximum value will be taken from the item tag if it exists. - */ - private static void set_damage_and_update_item(final CustomItem custom_item, final ItemStack item_stack, - int damage) { - // Honor unbreakable flag - final var ro_meta = item_stack.getItemMeta(); - if (ro_meta.isUnbreakable()) { - damage = 0; - } - set_damage_and_max_damage(custom_item, item_stack, damage); - } - - /** - * Initializes damage on the item, or removes them if custom durability - * is disabled for the given custom item. - */ - public static boolean initialize_or_update_max(final CustomItem custom_item, final ItemStack item_stack) { - // Remember damage if set. - var old_damage = item_stack.getItemMeta().getPersistentDataContainer().getOrDefault(ITEM_DURABILITY_DAMAGE, - PersistentDataType.INTEGER, -1); - - // First, remove all components. - item_stack.editMeta(meta -> { - final var data = meta.getPersistentDataContainer(); - data.remove(ITEM_DURABILITY_DAMAGE); - data.remove(ITEM_DURABILITY_MAX); - }); - - // The item has no durability anymore. Remove leftover lore and return. - if (custom_item.durability() <= 0) { - remove_lore(item_stack); - return false; - } - - final int actual_damage; - if (old_damage == -1) { - if (item_stack.getItemMeta() instanceof final Damageable damage_meta) { - // If there was no old damage value, initialize proportionally by visual damage. - final var visual_max = item_stack.getType().getMaxDurability(); - final var damage_percentage = (double) damage_meta.getDamage() / visual_max; - actual_damage = (int) (custom_item.durability() * damage_percentage); - } else { - // There was no old damage value, but the item has no visual durability. - // Initialize with max durability. - actual_damage = 0; - } - } else { - // Keep old damage. - actual_damage = old_damage; - } - - set_damage_and_update_item(custom_item, item_stack, actual_damage); - - return true; - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_item_damage(final PlayerItemDamageEvent event) { - final var item = event.getItem(); - final var custom_item = get_module().item_registry().get(item); - - // Ignore normal items - if (custom_item == null) { - return; - } - - update_damage(custom_item, item); - } - - /** - * Update existing max damage to match the configuration - */ - static public void update_damage(CustomItem custom_item, ItemStack item_stack) { - if (!(item_stack.getItemMeta() instanceof Damageable meta)) - return; // everything should be damageable now - - boolean updated = false; - PersistentDataContainer data = meta.getPersistentDataContainer(); - - final int new_max_damage = custom_item.durability() == 0 ? item_stack.getType().getMaxDurability() - : custom_item.durability(); - - int old_damage; - int old_max_damage; - // if the item has damage in their data, get the value and remove it from PDC - if (data.has(ITEM_DURABILITY_DAMAGE) && data.has(ITEM_DURABILITY_MAX)) { - old_damage = data.get(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER); - old_max_damage = data.get(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER); - updated = true; - } else { - old_damage = meta.hasDamage() ? meta.getDamage() : 0; - old_max_damage = meta.hasMaxDamage() ? meta.getMaxDamage() : item_stack.getType().getMaxDurability(); - } - - item_stack.editMeta(Damageable.class, imeta -> { - PersistentDataContainer idata = imeta.getPersistentDataContainer(); - idata.remove(ITEM_DURABILITY_DAMAGE); - idata.remove(ITEM_DURABILITY_MAX); - }); - - remove_lore(item_stack); - - if (!updated) - updated = old_max_damage != new_max_damage; // only update if there was old data or a different max - // durability - if (!updated) - return; // and do nothing if nothing changed - final int new_damage = scale_damage(old_damage, old_max_damage, new_max_damage); - set_damage_and_max_damage(custom_item, item_stack, new_damage); - } - - static public int scale_damage(int old_damage, int old_max_damage, int new_max_damage) { - return old_max_damage == new_max_damage ? old_damage - : (int) (new_max_damage * ((float) old_damage / (float) old_max_damage)); - } - - static public boolean set_damage_and_max_damage(CustomItem custom_item, ItemStack item, int damage) { - return item.editMeta(Damageable.class, meta -> { - if (custom_item.durability() != 0) { - meta.setMaxDamage(custom_item.durability()); - } else { - meta.setMaxDamage((int) item.getType().getMaxDurability()); - } - - meta.setDamage(damage); - }); - } + + public static final NamespacedKey ITEM_DURABILITY_MAX = StorageUtil.namespaced_key("vane", "durability.max"); + public static final NamespacedKey ITEM_DURABILITY_DAMAGE = StorageUtil.namespaced_key("vane", "durability.damage"); + + private static final NamespacedKey SENTINEL = StorageUtil.namespaced_key("vane", "durability_override_lore"); + + public DurabilityManager(final Context context) { + super(context); + } + + /** Returns true if the given component is associated to our custom durability. */ + private static boolean is_durability_lore(final Component component) { + return ItemUtil.has_sentinel(component, SENTINEL); + } + + /** Removes associated lore from an item. */ + private static void remove_lore(final ItemStack item_stack) { + final var lore = item_stack.lore(); + if (lore != null) { + lore.removeIf(DurabilityManager::is_durability_lore); + if (lore.size() > 0) { + item_stack.lore(lore); + } else { + item_stack.lore(null); + } + } + } + + /** + * Sets the item's damage regarding our custom durability. The durability will get clamped to + * plausible values. Damage values >= max will result in item breakage. The maximum value will + * be taken from the item tag if it exists. + */ + private static void set_damage_and_update_item( + final CustomItem custom_item, + final ItemStack item_stack, + int damage + ) { + // Honor unbreakable flag + final var ro_meta = item_stack.getItemMeta(); + if (ro_meta.isUnbreakable()) { + damage = 0; + } + set_damage_and_max_damage(custom_item, item_stack, damage); + } + + /** + * Initializes damage on the item, or removes them if custom durability is disabled for the + * given custom item. + */ + public static boolean initialize_or_update_max(final CustomItem custom_item, final ItemStack item_stack) { + // Remember damage if set. + var old_damage = item_stack + .getItemMeta() + .getPersistentDataContainer() + .getOrDefault(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER, -1); + + // First, remove all components. + item_stack.editMeta(meta -> { + final var data = meta.getPersistentDataContainer(); + data.remove(ITEM_DURABILITY_DAMAGE); + data.remove(ITEM_DURABILITY_MAX); + }); + + // The item has no durability anymore. Remove leftover lore and return. + if (custom_item.durability() <= 0) { + remove_lore(item_stack); + return false; + } + + final int actual_damage; + if (old_damage == -1) { + if (item_stack.getItemMeta() instanceof final Damageable damage_meta) { + // If there was no old damage value, initialize proportionally by visual damage. + final var visual_max = item_stack.getType().getMaxDurability(); + final var damage_percentage = (double) damage_meta.getDamage() / visual_max; + actual_damage = (int) (custom_item.durability() * damage_percentage); + } else { + // There was no old damage value, but the item has no visual durability. + // Initialize with max durability. + actual_damage = 0; + } + } else { + // Keep old damage. + actual_damage = old_damage; + } + + set_damage_and_update_item(custom_item, item_stack, actual_damage); + + return true; + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_item_damage(final PlayerItemDamageEvent event) { + final var item = event.getItem(); + final var custom_item = get_module().item_registry().get(item); + + // Ignore normal items + if (custom_item == null) { + return; + } + + update_damage(custom_item, item); + } + + /** Update existing max damage to match the configuration */ + public static void update_damage(CustomItem custom_item, ItemStack item_stack) { + if (!(item_stack.getItemMeta() instanceof Damageable meta)) return; // everything should be damageable now + + boolean updated = false; + PersistentDataContainer data = meta.getPersistentDataContainer(); + + final int new_max_damage = custom_item.durability() == 0 + ? item_stack.getType().getMaxDurability() + : custom_item.durability(); + + int old_damage; + int old_max_damage; + // if the item has damage in their data, get the value and remove it from PDC + if (data.has(ITEM_DURABILITY_DAMAGE) && data.has(ITEM_DURABILITY_MAX)) { + old_damage = data.get(ITEM_DURABILITY_DAMAGE, PersistentDataType.INTEGER); + old_max_damage = data.get(ITEM_DURABILITY_MAX, PersistentDataType.INTEGER); + updated = true; + } else { + old_damage = meta.hasDamage() ? meta.getDamage() : 0; + old_max_damage = meta.hasMaxDamage() ? meta.getMaxDamage() : item_stack.getType().getMaxDurability(); + } + + item_stack.editMeta(Damageable.class, imeta -> { + PersistentDataContainer idata = imeta.getPersistentDataContainer(); + idata.remove(ITEM_DURABILITY_DAMAGE); + idata.remove(ITEM_DURABILITY_MAX); + }); + + remove_lore(item_stack); + + if (!updated) updated = old_max_damage != new_max_damage; // only update if there was old data or a different + // max + // durability + if (!updated) return; // and do nothing if nothing changed + final int new_damage = scale_damage(old_damage, old_max_damage, new_max_damage); + set_damage_and_max_damage(custom_item, item_stack, new_damage); + } + + public static int scale_damage(int old_damage, int old_max_damage, int new_max_damage) { + return old_max_damage == new_max_damage + ? old_damage + : (int) (new_max_damage * ((float) old_damage / (float) old_max_damage)); + } + + public static boolean set_damage_and_max_damage(CustomItem custom_item, ItemStack item, int damage) { + return item.editMeta(Damageable.class, meta -> { + if (custom_item.durability() != 0) { + meta.setMaxDamage(custom_item.durability()); + } else { + meta.setMaxDamage((int) item.getType().getMaxDurability()); + } + + meta.setDamage(damage); + }); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java b/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java index 8db8d54ce..177f5e8d1 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/ExistingItemConverter.java @@ -15,125 +15,175 @@ import org.oddlama.vane.core.module.Context; public class ExistingItemConverter extends Listener { - public ExistingItemConverter(final Context context) { - super(context.namespace("existing_item_converter")); - } - - private CustomItem from_old_item(final ItemStack item_stack) { - final var meta = item_stack.getItemMeta(); - if (meta == null || !meta.hasCustomModelData()) { - return null; - } - - // If lookups fail, we return null and nothing will be done. - String new_item_key = null; - switch (meta.getCustomModelData()) { - case 7758190: new_item_key = "vane_trifles:wooden_sickle"; break; - case 7758191: new_item_key = "vane_trifles:stone_sickle"; break; - case 7758192: new_item_key = "vane_trifles:iron_sickle"; break; - case 7758193: new_item_key = "vane_trifles:golden_sickle"; break; - case 7758194: new_item_key = "vane_trifles:diamond_sickle"; break; - case 7758195: new_item_key = "vane_trifles:netherite_sickle"; break; - case 7758254: // fallthrough - case 7758255: // fallthrough - case 7758256: // fallthrough - case 7758257: // fallthrough - case 7758258: // fallthrough - case 7758259: new_item_key = "vane_trifles:file"; break; - case 7758318: new_item_key = "vane_trifles:empty_xp_bottle"; break; - case 7758382: new_item_key = "vane_trifles:small_xp_bottle"; break; - case 7758383: new_item_key = "vane_trifles:medium_xp_bottle"; break; - case 7758384: new_item_key = "vane_trifles:large_xp_bottle"; break; - case 7758446: new_item_key = "vane_trifles:home_scroll"; break; - case 7758510: new_item_key = "vane_trifles:unstable_scroll"; break; - case 7758574: new_item_key = "vane_trifles:reinforced_elytra"; break; - case 7823726: new_item_key = "vane_enchantments:ancient_tome"; break; - case 7823727: new_item_key = "vane_enchantments:enchanted_ancient_tome"; break; - case 7823790: new_item_key = "vane_enchantments:ancient_tome_of_knowledge"; break; - case 7823791: new_item_key = "vane_enchantments:enchanted_ancient_tome_of_knowledge"; break; - case 7823854: new_item_key = "vane_enchantments:ancient_tome_of_the_gods"; break; - case 7823855: new_item_key = "vane_enchantments:enchanted_ancient_tome_of_the_gods"; break; - } - - if (new_item_key == null) { - return null; - } - return get_module().item_registry().get(NamespacedKey.fromString(new_item_key)); - } - - private void process_inventory(@NotNull Inventory inventory) { - final var contents = inventory.getContents(); - int changed = 0; - - for (int i = 0; i < contents.length; ++i) { - final var is = contents[i]; - if (is == null || !is.hasItemMeta()) { - continue; - } - - final var custom_item = get_module().item_registry().get(is); - if (custom_item == null) { - // Determine if the item stack should be converted to a custom item from a legacy definition - final var convert_to_custom_item = from_old_item(is); - if (convert_to_custom_item == null) { - continue; - } - - contents[i] = convert_to_custom_item.convertExistingStack(is); - contents[i].editMeta(meta -> meta.itemName(convert_to_custom_item.displayName())); - get_module().enchantment_manager.update_enchanted_item(contents[i]); - get_module().log.info("Converted legacy item to " + convert_to_custom_item.key()); - ++changed; - continue; - } - - // Remove obsolete custom items - if (get_module().item_registry().shouldRemove(custom_item.key())) { - contents[i] = null; - get_module().log.info("Removed obsolete item " + custom_item.key()); - ++changed; - continue; - } - - // Update custom items to a new version, or if another detectable property changed. - final var key_and_version = CustomItemHelper.customItemTagsFromItemStack(is); - final var meta = is.getItemMeta(); - if (meta.getCustomModelData() != custom_item.customModelData() || - is.getType() != custom_item.baseMaterial() || - key_and_version.getRight() != custom_item.version() - ) { - // Also includes durability max update. - contents[i] = custom_item.convertExistingStack(is); - get_module().log.info("Updated item " + custom_item.key()); - ++changed; - continue; - } - - // Update maximum durability on existing items if changed. - Damageable damageableMeta = (Damageable) contents[i].getItemMeta(); - int max_damage = damageableMeta.hasMaxDamage() ? damageableMeta.getMaxDamage() : contents[i].getType().getMaxDurability(); - int correct_max_damage = custom_item.durability() == 0 ? contents[i].getType().getMaxDurability() : custom_item.durability(); - if (max_damage != correct_max_damage || meta.getPersistentDataContainer().has(DurabilityManager.ITEM_DURABILITY_DAMAGE)) { - get_module().log.info("Updated item durability " + custom_item.key()); - DurabilityManager.update_damage(custom_item, contents[i]); - ++changed; - continue; - } - } - - if (changed > 0) { - inventory.setContents(contents); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_join(final PlayerJoinEvent event) { - process_inventory(event.getPlayer().getInventory()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_inventory_open(final InventoryOpenEvent event) { - // Catches enderchests, and inventories by other plugins - process_inventory(event.getInventory()); - } + + public ExistingItemConverter(final Context context) { + super(context.namespace("existing_item_converter")); + } + + private CustomItem from_old_item(final ItemStack item_stack) { + final var meta = item_stack.getItemMeta(); + if (meta == null || !meta.hasCustomModelData()) { + return null; + } + + // If lookups fail, we return null and nothing will be done. + String new_item_key = null; + switch (meta.getCustomModelData()) { + case 7758190: + new_item_key = "vane_trifles:wooden_sickle"; + break; + case 7758191: + new_item_key = "vane_trifles:stone_sickle"; + break; + case 7758192: + new_item_key = "vane_trifles:iron_sickle"; + break; + case 7758193: + new_item_key = "vane_trifles:golden_sickle"; + break; + case 7758194: + new_item_key = "vane_trifles:diamond_sickle"; + break; + case 7758195: + new_item_key = "vane_trifles:netherite_sickle"; + break; + case 7758254: // fallthrough + case 7758255: // fallthrough + case 7758256: // fallthrough + case 7758257: // fallthrough + case 7758258: // fallthrough + case 7758259: + new_item_key = "vane_trifles:file"; + break; + case 7758318: + new_item_key = "vane_trifles:empty_xp_bottle"; + break; + case 7758382: + new_item_key = "vane_trifles:small_xp_bottle"; + break; + case 7758383: + new_item_key = "vane_trifles:medium_xp_bottle"; + break; + case 7758384: + new_item_key = "vane_trifles:large_xp_bottle"; + break; + case 7758446: + new_item_key = "vane_trifles:home_scroll"; + break; + case 7758510: + new_item_key = "vane_trifles:unstable_scroll"; + break; + case 7758574: + new_item_key = "vane_trifles:reinforced_elytra"; + break; + case 7823726: + new_item_key = "vane_enchantments:ancient_tome"; + break; + case 7823727: + new_item_key = "vane_enchantments:enchanted_ancient_tome"; + break; + case 7823790: + new_item_key = "vane_enchantments:ancient_tome_of_knowledge"; + break; + case 7823791: + new_item_key = "vane_enchantments:enchanted_ancient_tome_of_knowledge"; + break; + case 7823854: + new_item_key = "vane_enchantments:ancient_tome_of_the_gods"; + break; + case 7823855: + new_item_key = "vane_enchantments:enchanted_ancient_tome_of_the_gods"; + break; + } + + if (new_item_key == null) { + return null; + } + return get_module().item_registry().get(NamespacedKey.fromString(new_item_key)); + } + + private void process_inventory(@NotNull Inventory inventory) { + final var contents = inventory.getContents(); + int changed = 0; + + for (int i = 0; i < contents.length; ++i) { + final var is = contents[i]; + if (is == null || !is.hasItemMeta()) { + continue; + } + + final var custom_item = get_module().item_registry().get(is); + if (custom_item == null) { + // Determine if the item stack should be converted to a custom item from a legacy + // definition + final var convert_to_custom_item = from_old_item(is); + if (convert_to_custom_item == null) { + continue; + } + + contents[i] = convert_to_custom_item.convertExistingStack(is); + contents[i].editMeta(meta -> meta.itemName(convert_to_custom_item.displayName())); + get_module().enchantment_manager.update_enchanted_item(contents[i]); + get_module().log.info("Converted legacy item to " + convert_to_custom_item.key()); + ++changed; + continue; + } + + // Remove obsolete custom items + if (get_module().item_registry().shouldRemove(custom_item.key())) { + contents[i] = null; + get_module().log.info("Removed obsolete item " + custom_item.key()); + ++changed; + continue; + } + + // Update custom items to a new version, or if another detectable property changed. + final var key_and_version = CustomItemHelper.customItemTagsFromItemStack(is); + final var meta = is.getItemMeta(); + if ( + meta.getCustomModelData() != custom_item.customModelData() || + is.getType() != custom_item.baseMaterial() || + key_and_version.getRight() != custom_item.version() + ) { + // Also includes durability max update. + contents[i] = custom_item.convertExistingStack(is); + get_module().log.info("Updated item " + custom_item.key()); + ++changed; + continue; + } + + // Update maximum durability on existing items if changed. + Damageable damageableMeta = (Damageable) contents[i].getItemMeta(); + int max_damage = damageableMeta.hasMaxDamage() + ? damageableMeta.getMaxDamage() + : contents[i].getType().getMaxDurability(); + int correct_max_damage = custom_item.durability() == 0 + ? contents[i].getType().getMaxDurability() + : custom_item.durability(); + if ( + max_damage != correct_max_damage || + meta.getPersistentDataContainer().has(DurabilityManager.ITEM_DURABILITY_DAMAGE) + ) { + get_module().log.info("Updated item durability " + custom_item.key()); + DurabilityManager.update_damage(custom_item, contents[i]); + ++changed; + continue; + } + } + + if (changed > 0) { + inventory.setContents(contents); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_join(final PlayerJoinEvent event) { + process_inventory(event.getPlayer().getInventory()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_inventory_open(final InventoryOpenEvent event) { + // Catches enderchests, and inventories by other plugins + process_inventory(event.getInventory()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/VanillaFunctionalityInhibitor.java b/vane-core/src/main/java/org/oddlama/vane/core/item/VanillaFunctionalityInhibitor.java index fdd217eb7..40e52b047 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/VanillaFunctionalityInhibitor.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/VanillaFunctionalityInhibitor.java @@ -1,11 +1,9 @@ package org.oddlama.vane.core.item; import static org.oddlama.vane.util.MaterialUtil.is_tillable; -import static org.oddlama.vane.util.Nms.item_handle; import org.bukkit.Keyed; import org.bukkit.Material; -import org.bukkit.craftbukkit.entity.CraftItem; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; @@ -33,219 +31,221 @@ // TODO recipe book click event public class VanillaFunctionalityInhibitor extends Listener { - public VanillaFunctionalityInhibitor(Context context) { - super(context); - } - - private boolean inhibit(CustomItem custom_item, InhibitBehavior behavior) { - return custom_item != null && custom_item.enabled() && custom_item.inhibitedBehaviors().contains(behavior); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_pathfind(final EntityTargetEvent event) { - if (event.getReason() != EntityTargetEvent.TargetReason.TEMPT) { - return; - } - - if (event.getTarget() instanceof Player player) { - final var custom_item_main = get_module().item_registry().get(player.getInventory().getItemInMainHand()); - final var custom_item_off = get_module().item_registry().get(player.getInventory().getItemInOffHand()); - - if (inhibit(custom_item_main, InhibitBehavior.TEMPT) || inhibit(custom_item_off, InhibitBehavior.TEMPT)) { - event.setCancelled(true); - } - } - } - - // Prevent custom hoe items from tilling blocks - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_hoe_right_click_block(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - // Only when clicking a tillable block - if (!is_tillable(event.getClickedBlock().getType())) { - return; - } - - final var player = event.getPlayer(); - final var item = player.getEquipment().getItem(event.getHand()); - if (inhibit(get_module().item_registry().get(item), InhibitBehavior.HOE_TILL)) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_prepare_item_craft(final PrepareItemCraftEvent event) { - final var recipe = event.getRecipe(); - if (!(recipe instanceof Keyed keyed)) { - return; - } - - // Only consider canceling minecraft's recipes - if (!keyed.getKey().getNamespace().equals("minecraft")) { - return; - } - - for (final var item : event.getInventory().getMatrix()) { - if (inhibit(get_module().item_registry().get(item), InhibitBehavior.USE_IN_VANILLA_RECIPE)) { - event.getInventory().setResult(null); - return; - } - } - } - - // Prevent custom items from being used in smithing by default. They have to override this event to allow it. - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_prepare_smithing(final PrepareSmithingEvent event) { - final var item = event.getInventory().getInputEquipment(); - final var recipe = event.getInventory().getRecipe(); - if (!(recipe instanceof Keyed keyed)) { - return; - } - - // Only consider canceling minecraft's recipes - if (!keyed.getKey().getNamespace().equals("minecraft")) { - return; - } - - if (inhibit(get_module().item_registry().get(item), InhibitBehavior.USE_IN_VANILLA_RECIPE)) { - event.getInventory().setResult(null); - } - } - - // If the result of a smithing recipe is a custom item, copy and merge input NBT data. - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void on_prepare_smithing_copy_nbt(final PrepareSmithingEvent event) { - var result = event.getResult(); - final var recipe = event.getInventory().getRecipe(); - if (result == null || !(recipe instanceof SmithingRecipe smithing_recipe) || !smithing_recipe.willCopyNbt()) { - return; - } + + public VanillaFunctionalityInhibitor(Context context) { + super(context); + } + + private boolean inhibit(CustomItem custom_item, InhibitBehavior behavior) { + return custom_item != null && custom_item.enabled() && custom_item.inhibitedBehaviors().contains(behavior); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_pathfind(final EntityTargetEvent event) { + if (event.getReason() != EntityTargetEvent.TargetReason.TEMPT) { + return; + } + + if (event.getTarget() instanceof Player player) { + final var custom_item_main = get_module().item_registry().get(player.getInventory().getItemInMainHand()); + final var custom_item_off = get_module().item_registry().get(player.getInventory().getItemInOffHand()); + + if (inhibit(custom_item_main, InhibitBehavior.TEMPT) || inhibit(custom_item_off, InhibitBehavior.TEMPT)) { + event.setCancelled(true); + } + } + } + + // Prevent custom hoe items from tilling blocks + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_hoe_right_click_block(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + // Only when clicking a tillable block + if (!is_tillable(event.getClickedBlock().getType())) { + return; + } + + final var player = event.getPlayer(); + final var item = player.getEquipment().getItem(event.getHand()); + if (inhibit(get_module().item_registry().get(item), InhibitBehavior.HOE_TILL)) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_prepare_item_craft(final PrepareItemCraftEvent event) { + final var recipe = event.getRecipe(); + if (!(recipe instanceof Keyed keyed)) { + return; + } + + // Only consider canceling minecraft's recipes + if (!keyed.getKey().getNamespace().equals("minecraft")) { + return; + } + + for (final var item : event.getInventory().getMatrix()) { + if (inhibit(get_module().item_registry().get(item), InhibitBehavior.USE_IN_VANILLA_RECIPE)) { + event.getInventory().setResult(null); + return; + } + } + } + + // Prevent custom items from being used in smithing by default. They have to override this event + // to allow it. + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_prepare_smithing(final PrepareSmithingEvent event) { + final var item = event.getInventory().getInputEquipment(); + final var recipe = event.getInventory().getRecipe(); + if (!(recipe instanceof Keyed keyed)) { + return; + } + + // Only consider canceling minecraft's recipes + if (!keyed.getKey().getNamespace().equals("minecraft")) { + return; + } + + if (inhibit(get_module().item_registry().get(item), InhibitBehavior.USE_IN_VANILLA_RECIPE)) { + event.getInventory().setResult(null); + } + } + + // If the result of a smithing recipe is a custom item, copy and merge input NBT data. + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void on_prepare_smithing_copy_nbt(final PrepareSmithingEvent event) { + var result = event.getResult(); + final var recipe = event.getInventory().getRecipe(); + if (result == null || !(recipe instanceof SmithingRecipe smithing_recipe) || !smithing_recipe.willCopyNbt()) { + return; + } // Actually use a recipe result, as copynbt has already modified the result - result = recipe.getResult(); - final var custom_item_result = get_module().item_registry().get(result); - if (custom_item_result == null) { - return; - } - - final var input = event.getInventory().getInputEquipment(); - final var input_components = CraftItemStack.asNMSCopy(input).getComponents(); - final var nms_result = CraftItemStack.asNMSCopy(result); - nms_result.applyComponents(input_components); - - event.setResult(custom_item_result.convertExistingStack(CraftItemStack.asCraftMirror(nms_result))); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_prepare_anvil(final PrepareAnvilEvent event) { - final var a = event.getInventory().getFirstItem(); - final var b = event.getInventory().getSecondItem(); - - // Always prevent custom item repair with the custom item base material - // if it is not also a matching custom item. - // TODO: what about inventory based item repair? - if (a != null && b != null && a.getType() == b.getType()) { - // Disable the result unless a and b are instances of the same custom item. - final var custom_item_a = get_module().item_registry().get(a); - final var custom_item_b = get_module().item_registry().get(b); - if (custom_item_a != null && custom_item_a != custom_item_b) { - event.setResult(null); - return; - } - } - - final var r = event.getInventory().getResult(); - if (r != null) { - final var custom_item_r = get_module().item_registry().get(r); - final boolean[] did_edit = new boolean[]{ true }; - r.editMeta(meta -> { - if (a != null && inhibit(custom_item_r, InhibitBehavior.NEW_ENCHANTS)) { - for (final var ench : r.getEnchantments().keySet()) { - if (!a.getEnchantments().containsKey(ench)) { - meta.removeEnchant(ench); - did_edit[0] = true; - } - } - } - - if (inhibit(custom_item_r, InhibitBehavior.MEND)) { - meta.removeEnchant(Enchantment.MENDING); - did_edit[0] = true; - } - }); - - if (did_edit[0]) { - event.setResult(r); - } - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_item_mend(final PlayerItemMendEvent event) { - final var item = event.getItem(); - final var custom_item = get_module().item_registry().get(item); - - // No repairing for mending inhibited items. - if (inhibit(custom_item, InhibitBehavior.MEND)) { - event.setCancelled(true); - } - } - - // Prevent netherite items from burning, as they are made of netherite - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void on_item_burn(final EntityDamageEvent event) { - // Only burn damage on dropped items - if (event.getEntity().getType() != EntityType.ITEM) { - return; - } - - switch (event.getCause()) { - default: - return; - case FIRE: - case FIRE_TICK: - case LAVA: - break; - } - - final var entity = event.getEntity(); - if (!(entity instanceof Item)) { - return; - } - - final var item = ((Item)entity).getItemStack(); - if (inhibit(get_module().item_registry().get(item), InhibitBehavior.ITEM_BURN)) { - event.setCancelled(true); - } - } - - // Deny off-hand usage if the main hand is a custom item that inhibits off-hand usage. - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_right_click(final PlayerInteractEvent event) { - if (event.getHand() != EquipmentSlot.OFF_HAND) { - return; - } - - final var player = event.getPlayer(); - final var main_item = player.getEquipment().getItem(EquipmentSlot.HAND); - final var main_custom_item = get_module().item_registry().get(main_item); - if (inhibit(main_custom_item, InhibitBehavior.USE_OFFHAND)) { - event.setUseItemInHand(Event.Result.DENY); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_dispense(BlockDispenseEvent event) { - if(event.getBlock().getType() != Material.DISPENSER) { - return; - } - - final var custom_item = get_module().item_registry().get(event.getItem()); - if(inhibit(custom_item, InhibitBehavior.DISPENSE)) { - event.setCancelled(true); - } - } + result = recipe.getResult(); + final var custom_item_result = get_module().item_registry().get(result); + if (custom_item_result == null) { + return; + } + + final var input = event.getInventory().getInputEquipment(); + final var input_components = CraftItemStack.asNMSCopy(input).getComponents(); + final var nms_result = CraftItemStack.asNMSCopy(result); + nms_result.applyComponents(input_components); + + event.setResult(custom_item_result.convertExistingStack(CraftItemStack.asCraftMirror(nms_result))); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_prepare_anvil(final PrepareAnvilEvent event) { + final var a = event.getInventory().getFirstItem(); + final var b = event.getInventory().getSecondItem(); + + // Always prevent custom item repair with the custom item base material + // if it is not also a matching custom item. + // TODO: what about inventory based item repair? + if (a != null && b != null && a.getType() == b.getType()) { + // Disable the result unless a and b are instances of the same custom item. + final var custom_item_a = get_module().item_registry().get(a); + final var custom_item_b = get_module().item_registry().get(b); + if (custom_item_a != null && custom_item_a != custom_item_b) { + event.setResult(null); + return; + } + } + + final var r = event.getInventory().getResult(); + if (r != null) { + final var custom_item_r = get_module().item_registry().get(r); + final boolean[] did_edit = new boolean[] { true }; + r.editMeta(meta -> { + if (a != null && inhibit(custom_item_r, InhibitBehavior.NEW_ENCHANTS)) { + for (final var ench : r.getEnchantments().keySet()) { + if (!a.getEnchantments().containsKey(ench)) { + meta.removeEnchant(ench); + did_edit[0] = true; + } + } + } + + if (inhibit(custom_item_r, InhibitBehavior.MEND)) { + meta.removeEnchant(Enchantment.MENDING); + did_edit[0] = true; + } + }); + + if (did_edit[0]) { + event.setResult(r); + } + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_item_mend(final PlayerItemMendEvent event) { + final var item = event.getItem(); + final var custom_item = get_module().item_registry().get(item); + + // No repairing for mending inhibited items. + if (inhibit(custom_item, InhibitBehavior.MEND)) { + event.setCancelled(true); + } + } + + // Prevent netherite items from burning, as they are made of netherite + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void on_item_burn(final EntityDamageEvent event) { + // Only burn damage on dropped items + if (event.getEntity().getType() != EntityType.ITEM) { + return; + } + + switch (event.getCause()) { + default: + return; + case FIRE: + case FIRE_TICK: + case LAVA: + break; + } + + final var entity = event.getEntity(); + if (!(entity instanceof Item)) { + return; + } + + final var item = ((Item) entity).getItemStack(); + if (inhibit(get_module().item_registry().get(item), InhibitBehavior.ITEM_BURN)) { + event.setCancelled(true); + } + } + + // Deny off-hand usage if the main hand is a custom item that inhibits off-hand usage. + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_right_click(final PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.OFF_HAND) { + return; + } + + final var player = event.getPlayer(); + final var main_item = player.getEquipment().getItem(EquipmentSlot.HAND); + final var main_custom_item = get_module().item_registry().get(main_item); + if (inhibit(main_custom_item, InhibitBehavior.USE_OFFHAND)) { + event.setUseItemInHand(Event.Result.DENY); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_dispense(BlockDispenseEvent event) { + if (event.getBlock().getType() != Material.DISPENSER) { + return; + } + + final var custom_item = get_module().item_registry().get(event.getItem()); + if (inhibit(custom_item, InhibitBehavior.DISPENSE)) { + event.setCancelled(true); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItem.java b/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItem.java index be938fb98..b04ed0020 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItem.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItem.java @@ -2,7 +2,10 @@ import java.io.IOException; import java.util.EnumSet; - +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; @@ -11,148 +14,144 @@ import org.oddlama.vane.core.item.CustomItemHelper; import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TranslatableComponent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; - /** - * This is the CustomItem specification that all custom items must implement to be registered with the vane custom-item API. + * This is the CustomItem specification that all custom items must implement to be registered with + * the vane custom-item API. */ public interface CustomItem { - /** - * The unique identifier for this custom item. This is the only value that can never be changed after initial item registration. - * If you want to deprecate an item, you can force your subclass to always return false in {@link #enabled()}. This will cause existing - * items to behave as their base items. To completely remove a custom item from encountered inventories, you can - * queue it for removal using {@link CustomItemRegistry#removePermanently(NamespacedKey)}. - * The key is the primary identifier of an item and must never change after initial registration. - * All other properties can be safely changed between restarts and even on configuration reloads. - */ - public NamespacedKey key(); - - /** - * Return true to indicate that this item is enabled. If this function returns false, - * the custom item will be regarded as nonexistent. This means that in this case, - * no item-stack updates are executed, and no related events are processed internally. - */ - public boolean enabled(); - - /** - * Returns a version number of this item. Increasing this value will cause {@link #convertExistingStack(ItemStack)} - * to be called on any encountered itemstack of this custom item. Useful to force an update of existing - * items when you, for example, change the {@link #updateItemStack(ItemStack)} function to add custom - * properties to your custom item. - */ - public int version(); - - /** - * Returns the base material that the custom item is made of. - * If this is changed, any encountered item will automatically be updated. - * - * To ensure that a breakage of the plugin never creates value for players, - * use a material with less net-worth than what the custom item provides. - * For crafting ingredients, we recommend using an item that has no other use than in crafting. - * Generally, it is a good idea to pick materials that have an item with similar properties. - * - * By default, no attempts will be made to remove the vanilla behavior of the base items, - * except for inhibiting use in crafting recipes that require the base material. - * See {@link #inhibitedBehaviors()} for more information. If you require other - * behaviors to be inhibited, you need to write your own event listeners. - */ - public Material baseMaterial(); - - /** - * Returns the custom model data used as a selector in a resource pack. - * If this is changed, any encountered item will automatically be updated. - * We recommend reserving a set of ids using {@link CustomModelDataRegistry#reserveRange(NamespacedKey, int, int)} when your plugin starts. - * This allows you to freely use and re-use the registered ids without having to worry about clashes with other plugins. - */ - public int customModelData(); - - - /** - * Returns the display name for newly created items of this type. - * This will only NOT be updated on existing items. If you want that behavior, you - * can easily implement it by overriding {@link #updateItemStack(ItemStack)}. - * By using translatable components, there's no need to update this except when changing the translation key. - */ - public @Nullable Component displayName(); - - /** - * A custom translation key that will be used to display durability on the item. - * Return null to disable durability lore. Arguments to the translatable component - * that will be supplied are the current durability (%1$s) and max durability (%2$s). - */ - default public @Nullable TranslatableComponent durabilityLore() { - return Component.translatable("item.durability") - .color(NamedTextColor.WHITE) - .decoration(TextDecoration.ITALIC, false); - } - - /** - * The item's effective maximum durability. If this returns 0, no changes will be made to the base - * item's durability mechanic. If this is set to a value > 0, it requires a base item with durability. - * The durability bar of the base item then acts solely as an indicative value of a separately stored durability. - * Changes to the item's durability by classical means are automatically reflected in this property. - * - * If this value is changed while item stacks of this custom item already exist with a different maximum durability, - * the affected items will be updated and keep their current durability, but clamped to the new maximum. - */ - public int durability(); - - /** - * Specifies which vanilla behaviors should be inhibited for this custom item. - * - * By default, any _vanilla_ crafting recipe (i.e. "minecraft" is the associated key's namespace) - * will be disabled when this custom item is used in the recipe's inputs, as well as any - * smithing recipe with this item as any input. - * - * So a custom item based on paper as the base material cannot be used to craft, - * for example, * books (which require paper as an ingredient), - * or a diamond hoe- * based item will not be converted to a vanilla netherite hoe in the smithing table. - * - * If you require a hoe that doesn't till blocks or a carrot/fungus on a stick - * that doesn't attract certain entities, you can override {@link #inhibitedBehaviors()} - * to specify what should be prohibited. - * The user must handle anything else. - */ - default public EnumSet inhibitedBehaviors() { - return EnumSet.of(InhibitBehavior.USE_IN_VANILLA_RECIPE); - } - - /** - * This function will be called when the resource pack is generated, and allows you - * to add the item's texture, translation strings or any other client side resources to a pack - * that can/will be distributed to players. - */ - public void addResources(final ResourcePackGenerator rp) throws IOException; - - /** - * This function will be called when a custom item of this type is newly created, - * or when an existing stack needs to be updated. This can include cases where - * no base information actually changed, but an item still is considered to - * be updated, for example, anvil results. - */ - default public ItemStack updateItemStack(@NotNull final ItemStack itemStack) { - return itemStack; - } - - /** - * Returns true if the given itemstack is an "instance" this custom item. - */ - default public boolean isInstance(@Nullable final ItemStack itemStack) { - return CustomItemRegistry.instance().get(itemStack) == this; - } - - public default ItemStack newStack() { - return CustomItemHelper.newStack(this); - } - - public default ItemStack newStack(final int amount) { - return CustomItemHelper.newStack(this, amount); - } - - public default ItemStack convertExistingStack(final ItemStack item_stack) { - return CustomItemHelper.convertExistingStack(this, item_stack); - } + /** + * The unique identifier for this custom item. This is the only value that can never be changed + * after initial item registration. If you want to deprecate an item, you can force your + * subclass to always return false in {@link #enabled()}. This will cause existing items to + * behave as their base items. To completely remove a custom item from encountered inventories, + * you can queue it for removal using {@link + * CustomItemRegistry#removePermanently(NamespacedKey)}. The key is the primary identifier of an + * item and must never change after initial registration. All other properties can be safely + * changed between restarts and even on configuration reloads. + */ + public NamespacedKey key(); + + /** + * Return true to indicate that this item is enabled. If this function returns false, the custom + * item will be regarded as nonexistent. This means that in this case, no item-stack updates are + * executed, and no related events are processed internally. + */ + public boolean enabled(); + + /** + * Returns a version number of this item. Increasing this value will cause {@link + * #convertExistingStack(ItemStack)} to be called on any encountered itemstack of this custom + * item. Useful to force an update of existing items when you, for example, change the {@link + * #updateItemStack(ItemStack)} function to add custom properties to your custom item. + */ + public int version(); + + /** + * Returns the base material that the custom item is made of. If this is changed, any + * encountered item will automatically be updated. + * + *

To ensure that a breakage of the plugin never creates value for players, use a material + * with less net-worth than what the custom item provides. For crafting ingredients, we + * recommend using an item that has no other use than in crafting. Generally, it is a good idea + * to pick materials that have an item with similar properties. + * + *

By default, no attempts will be made to remove the vanilla behavior of the base items, + * except for inhibiting use in crafting recipes that require the base material. See {@link + * #inhibitedBehaviors()} for more information. If you require other behaviors to be inhibited, + * you need to write your own event listeners. + */ + public Material baseMaterial(); + + /** + * Returns the custom model data used as a selector in a resource pack. If this is changed, any + * encountered item will automatically be updated. We recommend reserving a set of ids using + * {@link CustomModelDataRegistry#reserveRange(NamespacedKey, int, int)} when your plugin + * starts. This allows you to freely use and re-use the registered ids without having to worry + * about clashes with other plugins. + */ + public int customModelData(); + + /** + * Returns the display name for newly created items of this type. This will only NOT be updated + * on existing items. If you want that behavior, you can easily implement it by overriding + * {@link #updateItemStack(ItemStack)}. By using translatable components, there's no need to + * update this except when changing the translation key. + */ + public @Nullable Component displayName(); + + /** + * A custom translation key that will be used to display durability on the item. Return null to + * disable durability lore. Arguments to the translatable component that will be supplied are + * the current durability (%1$s) and max durability (%2$s). + */ + public default @Nullable TranslatableComponent durabilityLore() { + return Component.translatable("item.durability") + .color(NamedTextColor.WHITE) + .decoration(TextDecoration.ITALIC, false); + } + + /** + * The item's effective maximum durability. If this returns 0, no changes will be made to the + * base item's durability mechanic. If this is set to a value > 0, it requires a base item with + * durability. The durability bar of the base item then acts solely as an indicative value of a + * separately stored durability. Changes to the item's durability by classical means are + * automatically reflected in this property. + * + *

If this value is changed while item stacks of this custom item already exist with a + * different maximum durability, the affected items will be updated and keep their current + * durability, but clamped to the new maximum. + */ + public int durability(); + + /** + * Specifies which vanilla behaviors should be inhibited for this custom item. + * + *

By default, any _vanilla_ crafting recipe (i.e. "minecraft" is the associated key's + * namespace) will be disabled when this custom item is used in the recipe's inputs, as well as + * any smithing recipe with this item as any input. + * + *

So a custom item based on paper as the base material cannot be used to craft, for example, + * * books (which require paper as an ingredient), or a diamond hoe- * based item will not be + * converted to a vanilla netherite hoe in the smithing table. + * + *

If you require a hoe that doesn't till blocks or a carrot/fungus on a stick that doesn't + * attract certain entities, you can override {@link #inhibitedBehaviors()} to specify what + * should be prohibited. The user must handle anything else. + */ + public default EnumSet inhibitedBehaviors() { + return EnumSet.of(InhibitBehavior.USE_IN_VANILLA_RECIPE); + } + + /** + * This function will be called when the resource pack is generated, and allows you to add the + * item's texture, translation strings or any other client side resources to a pack that + * can/will be distributed to players. + */ + public void addResources(final ResourcePackGenerator rp) throws IOException; + + /** + * This function will be called when a custom item of this type is newly created, or when an + * existing stack needs to be updated. This can include cases where no base information actually + * changed, but an item still is considered to be updated, for example, anvil results. + */ + public default ItemStack updateItemStack(@NotNull final ItemStack itemStack) { + return itemStack; + } + + /** Returns true if the given itemstack is an "instance" this custom item. */ + public default boolean isInstance(@Nullable final ItemStack itemStack) { + return CustomItemRegistry.instance().get(itemStack) == this; + } + + public default ItemStack newStack() { + return CustomItemHelper.newStack(this); + } + + public default ItemStack newStack(final int amount) { + return CustomItemHelper.newStack(this, amount); + } + + public default ItemStack convertExistingStack(final ItemStack item_stack) { + return CustomItemHelper.convertExistingStack(this, item_stack); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItemRegistry.java b/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItemRegistry.java index 2c6a85b2c..c03bf7fe6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItemRegistry.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomItemRegistry.java @@ -1,77 +1,69 @@ package org.oddlama.vane.core.item.api; import java.util.Collection; - import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; import org.oddlama.vane.core.Core; -/** - * This is the registry with which you can register your custom items. - */ +/** This is the registry with which you can register your custom items. */ public interface CustomItemRegistry { - /** - * Returns true if a custom item with the given resourceKey has been registered. - */ - public @Nullable boolean has(NamespacedKey resourceKey); + /** Returns true if a custom item with the given resourceKey has been registered. */ + public @Nullable boolean has(NamespacedKey resourceKey); - /** - * Returns all registered custom items. - */ - public @Nullable Collection all(); + /** Returns all registered custom items. */ + public @Nullable Collection all(); - /** - * Tries to retrieve a custom item definition by resource key. - * Returns null if no such definition exists. - */ - public @Nullable CustomItem get(NamespacedKey resourceKey); + /** + * Tries to retrieve a custom item definition by resource key. Returns null if no such + * definition exists. + */ + public @Nullable CustomItem get(NamespacedKey resourceKey); - /** - * Tries to retrieve a custom item definition from an ItemStack. - * Returns null if the itemstack is not a custom item, or references a custom item - * that has not been registered (e.g., previously installed plugin). - */ - // TODO: make command /clearcustomitems namespace:key that queues an item for deletion even if the original plugin is gone now. Maybe even allow clearing a whole namespace. - // TODO: for an immediate operation on a whole world, NBTExplorer can be used together with a removal filter filtering on the custom item id. - public @Nullable CustomItem get(@Nullable ItemStack itemStack); + /** + * Tries to retrieve a custom item definition from an ItemStack. Returns null if the itemstack + * is not a custom item, or references a custom item that has not been registered (e.g., + * previously installed plugin). + */ + // TODO: make command /clearcustomitems namespace:key that queues an item for deletion even if + // the original plugin is gone now. Maybe even allow clearing a whole namespace. + // TODO: for an immediate operation on a whole world, NBTExplorer can be used together with a + // removal filter filtering on the custom item id. + public @Nullable CustomItem get(@Nullable ItemStack itemStack); - /** - * Registers a new custom item. Throws an IllegalArgumentException if an - * item with the same key has already been registered. - */ - public void register(CustomItem customItem); + /** + * Registers a new custom item. Throws an IllegalArgumentException if an item with the same key + * has already been registered. + */ + public void register(CustomItem customItem); - /** - * Queues removal of a given custom item. If any matching item is encountered - * in the future, it will be removed permanently from the respective inventory. - * - * This is not a one-off operation! Removal only actually happens when the item - * is encountered due to a player interacting with an inventory. This is intended - * as a way for plugins to queue removal of items from old plugin versions. - */ - public void removePermanently(NamespacedKey key); + /** + * Queues removal of a given custom item. If any matching item is encountered in the future, it + * will be removed permanently from the respective inventory. + * + *

This is not a one-off operation! Removal only actually happens when the item is + * encountered due to a player interacting with an inventory. This is intended as a way for + * plugins to queue removal of items from old plugin versions. + */ + public void removePermanently(NamespacedKey key); - /** - * Returns true if the associated key was queued for removal using {@link #removePermanently(NamespacedKey)}. - */ - public boolean shouldRemove(NamespacedKey key); + /** + * Returns true if the associated key was queued for removal using {@link + * #removePermanently(NamespacedKey)}. + */ + public boolean shouldRemove(NamespacedKey key); - /** - * Returns the custom model data registry. - */ - public CustomModelDataRegistry dataRegistry(); + /** Returns the custom model data registry. */ + public CustomModelDataRegistry dataRegistry(); - /** - * Retrieves the global registry instance from the running vane-core instance, if any. - */ - public static CustomItemRegistry instance() { - return Core.instance().item_registry(); - //final var core = Bukkit.getServer().getPluginManager().getPlugin("vane-core"); - //if (core == null) { - // return Optional.empty(); - //} + /** Retrieves the global registry instance from the running vane-core instance, if any. */ + public static CustomItemRegistry instance() { + return Core.instance().item_registry(); + // final var core = Bukkit.getServer().getPluginManager().getPlugin("vane-core"); + // if (core == null) { + // return Optional.empty(); + // } - //return Optional.of(((Core)core).item_registry()); - } + // return Optional.of(((Core)core).item_registry()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomModelDataRegistry.java b/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomModelDataRegistry.java index 0bf7d06c3..b45077b91 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomModelDataRegistry.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/api/CustomModelDataRegistry.java @@ -4,62 +4,51 @@ import org.jetbrains.annotations.Nullable; public interface CustomModelDataRegistry { - /** - * A range of custom model data ids. - */ - public static record Range(int from, int to) { - public Range { - if (from >= to) { - throw new IllegalArgumentException("A range must contain at least one integer"); - } - if (from <= -(1 << 24)) { - throw new IllegalArgumentException("A range cannot contain a number <= -2^24, as these cannot be accurately represented in JSON."); - } - if (to >= (1 << 24)) { - throw new IllegalArgumentException("A range cannot contain a number >= 2^24, as these cannot be accurately represented in JSON."); - } - } - - public boolean contains(int data) { - return data >= from && data < to; - } - public boolean overlaps(Range range) { - return !(to >= range.from || from >= range.to); - } - } - - /** - * Returns true if the given custom model data is already reserved. - */ - public boolean has(int data); - - /** - * Returns true if any custom model data in the given range is already reserved. - */ - public boolean hasAny(Range range); - - /** - * Returns the range associated to a specific key. - */ - public Range get(NamespacedKey resourceKey); - - /** - * Returns the key associated to specific custom model data, if any. - */ - public @Nullable NamespacedKey get(int data); - - /** - * Returns the key associated to the first encountered registered id in the given range. - */ - public @Nullable NamespacedKey get(Range range); - - /** - * Reserves the given range. - */ - public void reserve(NamespacedKey resourceKey, Range range); - - /** - * Reserves the given range. - */ - public void reserveSingle(NamespacedKey resourceKey, int data); + /** A range of custom model data ids. */ + public static record Range(int from, int to) { + public Range { + if (from >= to) { + throw new IllegalArgumentException("A range must contain at least one integer"); + } + if (from <= -(1 << 24)) { + throw new IllegalArgumentException( + "A range cannot contain a number <= -2^24, as these cannot be accurately represented in JSON." + ); + } + if (to >= (1 << 24)) { + throw new IllegalArgumentException( + "A range cannot contain a number >= 2^24, as these cannot be accurately represented in JSON." + ); + } + } + + public boolean contains(int data) { + return data >= from && data < to; + } + + public boolean overlaps(Range range) { + return !(to >= range.from || from >= range.to); + } + } + + /** Returns true if the given custom model data is already reserved. */ + public boolean has(int data); + + /** Returns true if any custom model data in the given range is already reserved. */ + public boolean hasAny(Range range); + + /** Returns the range associated to a specific key. */ + public Range get(NamespacedKey resourceKey); + + /** Returns the key associated to specific custom model data, if any. */ + public @Nullable NamespacedKey get(int data); + + /** Returns the key associated to the first encountered registered id in the given range. */ + public @Nullable NamespacedKey get(Range range); + + /** Reserves the given range. */ + public void reserve(NamespacedKey resourceKey, Range range); + + /** Reserves the given range. */ + public void reserveSingle(NamespacedKey resourceKey, int data); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/item/api/InhibitBehavior.java b/vane-core/src/main/java/org/oddlama/vane/core/item/api/InhibitBehavior.java index cbefbd2a5..7d8414a85 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/item/api/InhibitBehavior.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/item/api/InhibitBehavior.java @@ -1,26 +1,28 @@ package org.oddlama.vane.core.item.api; public enum InhibitBehavior { - /** - * Prevents the item from being used as an ingredient in - * any (recognized) vanilla crafting recipe, i.e., recipe in the "minecraft" namespace. - */ - USE_IN_VANILLA_RECIPE, - /** - * Prevents the item from causing any entities to target and walk to - * the player when the item is held in the main or offhand. - */ - TEMPT, - /** Prevents the item from burning. */ - ITEM_BURN, - /** Prevents hoes from creating farmland. */ - HOE_TILL, - /** Prevents players from using the offhand item when holding this in the main hand. */ - USE_OFFHAND, - /** Prevents players from mending this item (and prevents it from gaining mending in the anvil). */ - MEND, - /** Prevents players from adding new enchantments via the anvil. */ - NEW_ENCHANTS, - /** Prevents dispensers from dispense this item */ - DISPENSE + /** + * Prevents the item from being used as an ingredient in any (recognized) vanilla crafting + * recipe, i.e., recipe in the "minecraft" namespace. + */ + USE_IN_VANILLA_RECIPE, + /** + * Prevents the item from causing any entities to target and walk to the player when the item is + * held in the main or offhand. + */ + TEMPT, + /** Prevents the item from burning. */ + ITEM_BURN, + /** Prevents hoes from creating farmland. */ + HOE_TILL, + /** Prevents players from using the offhand item when holding this in the main hand. */ + USE_OFFHAND, + /** + * Prevents players from mending this item (and prevents it from gaining mending in the anvil). + */ + MEND, + /** Prevents players from adding new enchantments via the anvil. */ + NEW_ENCHANTS, + /** Prevents dispensers from dispense this item */ + DISPENSE, } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangField.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangField.java index 89b6c52d2..859af2422 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangField.java @@ -3,83 +3,83 @@ import java.lang.reflect.Field; import java.util.function.Function; import org.bukkit.configuration.file.YamlConfiguration; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.YamlLoadException; import org.oddlama.vane.core.module.Module; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; public abstract class LangField { - public static final String PREFIX = "lang_"; - - private Module module; - protected Object owner; - protected Field field; - protected String name; - private final String namespace; - private final String key; - - public LangField(Module module, Object owner, Field field, Function map_name) { - this.module = module; - this.owner = owner; - this.field = field; - - if (!field.getName().contains(PREFIX)) throw new RuntimeException( - new YamlLoadException.Lang("field must start with " + PREFIX, this) - ); - this.name = map_name.apply(field.getName().substring(PREFIX.length())); - this.namespace = module.namespace(); - this.key = namespace + "." + yaml_path(); - - field.setAccessible(true); - } - - public String get_name() { - return name; - } - - public String yaml_path() { - return name; - } - - protected void check_yaml_path(YamlConfiguration yaml) throws YamlLoadException { - if (!yaml.contains(name, true)) { - throw new YamlLoadException.Lang("yaml is missing entry with path '" + name + "'", this); - } - } - - public Module module() { - return module; - } - - public String namespace() { - return namespace; - } - - public String key() { - return key; - } - - public abstract void check_loadable(YamlConfiguration yaml) throws YamlLoadException; - - public abstract void load(final String namespace, final YamlConfiguration yaml); - - public abstract void add_translations( - final ResourcePackGenerator pack, - final YamlConfiguration yaml, - String lang_code - ) throws YamlLoadException; - - @SuppressWarnings("unchecked") - public T get() { - try { - return (T) field.get(owner); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } - - @Override - public String toString() { - return (field.getDeclaringClass().getTypeName() + "::" + field.getName()); - } + public static final String PREFIX = "lang_"; + + private Module module; + protected Object owner; + protected Field field; + protected String name; + private final String namespace; + private final String key; + + public LangField(Module module, Object owner, Field field, Function map_name) { + this.module = module; + this.owner = owner; + this.field = field; + + if (!field.getName().contains(PREFIX)) throw new RuntimeException( + new YamlLoadException.Lang("field must start with " + PREFIX, this) + ); + this.name = map_name.apply(field.getName().substring(PREFIX.length())); + this.namespace = module.namespace(); + this.key = namespace + "." + yaml_path(); + + field.setAccessible(true); + } + + public String get_name() { + return name; + } + + public String yaml_path() { + return name; + } + + protected void check_yaml_path(YamlConfiguration yaml) throws YamlLoadException { + if (!yaml.contains(name, true)) { + throw new YamlLoadException.Lang("yaml is missing entry with path '" + name + "'", this); + } + } + + public Module module() { + return module; + } + + public String namespace() { + return namespace; + } + + public String key() { + return key; + } + + public abstract void check_loadable(YamlConfiguration yaml) throws YamlLoadException; + + public abstract void load(final String namespace, final YamlConfiguration yaml); + + public abstract void add_translations( + final ResourcePackGenerator pack, + final YamlConfiguration yaml, + String lang_code + ) throws YamlLoadException; + + @SuppressWarnings("unchecked") + public T get() { + try { + return (T) field.get(owner); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } + + @Override + public String toString() { + return (field.getDeclaringClass().getTypeName() + "::" + field.getName()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangManager.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangManager.java index 5c7bee371..756f2de57 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangManager.java @@ -15,185 +15,186 @@ import org.oddlama.vane.annotation.lang.LangMessage; import org.oddlama.vane.annotation.lang.LangMessageArray; import org.oddlama.vane.annotation.lang.LangVersion; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.YamlLoadException; import org.oddlama.vane.core.module.Module; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; public class LangManager { - Module module; - private List> lang_fields = new ArrayList<>(); - LangVersionField field_version; - - public LangManager(Module module) { - this.module = module; - compile(module, s -> s); - } - - public long expected_version() { - return module.annotation.lang_version(); - } - - private boolean has_lang_annotation(Field field) { - for (var a : field.getAnnotations()) { - if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.lang.Lang")) { - return true; - } - } - return false; - } - - private void assert_field_prefix(Field field) { - if (!field.getName().startsWith("lang_")) { - throw new RuntimeException("Language fields must be prefixed lang_. This is a bug."); - } - } - - private LangField compile_field(Object owner, Field field, Function map_name) { - assert_field_prefix(field); - - // Get the annotation - Annotation annotation = null; - for (var a : field.getAnnotations()) { - if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.lang.Lang")) { - if (annotation == null) { - annotation = a; - } else { - throw new RuntimeException("Language fields must have exactly one @Lang annotation."); - } - } - } - assert annotation != null; - final var atype = annotation.annotationType(); - - // Return a correct wrapper object - if (atype.equals(LangMessage.class)) { - return new LangMessageField(module, owner, field, map_name, (LangMessage) annotation); - } else if (atype.equals(LangMessageArray.class)) { - return new LangMessageArrayField(module, owner, field, map_name, (LangMessageArray) annotation); - } else if (atype.equals(LangVersion.class)) { - if (owner != module) { - throw new RuntimeException("@LangVersion can only be used inside the main module. This is a bug."); - } - if (field_version != null) { - throw new RuntimeException( - "There must be exactly one @LangVersion field! (found multiple). This is a bug." - ); - } - return field_version = new LangVersionField(module, owner, field, map_name, (LangVersion) annotation); - } else { - throw new RuntimeException("Missing LangField handler for @" + atype.getName() + ". This is a bug."); - } - } - - private boolean verify_version(File file, long version) { - if (version != expected_version()) { - module.log.severe(file.getName() + ": expected version " + expected_version() + ", but got " + version); - - if (version == 0) { - module.log.severe("Something went wrong while generating or loading the configuration."); - module.log.severe("If you are sure your configuration is correct and this isn't a file"); - module.log.severe( - "system permission issue, please report this to https://github.com/oddlama/vane/issues" - ); - } else if (version < expected_version()) { - module.log.severe("This language file is for an older version of " + module.getName() + "."); - module.log.severe("Please update your file or use an officially supported language file."); - } else { - module.log.severe("This language file is for a future version of " + module.getName() + "."); - module.log.severe("Please use the correct file for this version, or use an officially"); - module.log.severe("supported language file."); - } - - return false; - } - - return true; - } - - @SuppressWarnings("unchecked") - public void compile(Object owner, Function map_name) { - // Compile all annotated fields - lang_fields.addAll( - getAllFields(owner.getClass()) - .stream() - .filter(this::has_lang_annotation) - .map(f -> compile_field(owner, f, map_name)).toList() - ); - - if (owner == module && field_version == null) { - throw new RuntimeException("There must be exactly one @LangVersion field! (found none). This is a bug."); - } - } - - @SuppressWarnings("unchecked") - public T get_field(String name) { - var field = lang_fields - .stream() - .filter(f -> f.get_name().equals(name)) - .findFirst() - .orElseThrow(() -> new RuntimeException("Missing lang field lang_" + name)); - - try { - return (T) field; - } catch (ClassCastException e) { - throw new RuntimeException("Invalid lang field type for lang_" + name, e); - } - } - - public boolean reload(File file) { - // Load file - final var yaml = YamlConfiguration.loadConfiguration(file); - - // Check version - final var version = yaml.getLong("version", -1); - if (!verify_version(file, version)) { - return false; - } - - try { - // Check languration for errors - for (var f : lang_fields) { - f.check_loadable(yaml); - } - - for (var f : lang_fields) { - f.load(module.namespace(), yaml); - } - } catch (YamlLoadException e) { - module.log.log(Level.SEVERE, "error while loading '" + file.getAbsolutePath() + "'", e); - return false; - } - return true; - } - - public void generate_resource_pack(final ResourcePackGenerator pack, YamlConfiguration yaml, File lang_file) { - var lang_code = yaml.getString("resource_pack_lang_code"); - if (lang_code == null) { - throw new RuntimeException("Missing yaml key: resource_pack_lang_code"); - } - var errors = new LinkedList(); - for (var f : lang_fields) { - try { - f.add_translations(pack, yaml, lang_code); - } catch (YamlLoadException.Lang e) { - errors.add(e); - } catch (YamlLoadException e) { - module.log.log(Level.SEVERE, "Unexpected YAMLLoadException: ", e); - } - } - if (errors.size() > 0) { - final String errored_lang_nodes = errors - .stream() - .map(Throwable::getMessage) - .collect(Collectors.joining("\n\t\t")); - module.log.log( - Level.SEVERE, - "The following errors were identified while adding translations from \n\t" + - lang_file.getAbsolutePath() + - " \n\t\t" + - errored_lang_nodes - ); - } - } + Module module; + private List> lang_fields = new ArrayList<>(); + LangVersionField field_version; + + public LangManager(Module module) { + this.module = module; + compile(module, s -> s); + } + + public long expected_version() { + return module.annotation.lang_version(); + } + + private boolean has_lang_annotation(Field field) { + for (var a : field.getAnnotations()) { + if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.lang.Lang")) { + return true; + } + } + return false; + } + + private void assert_field_prefix(Field field) { + if (!field.getName().startsWith("lang_")) { + throw new RuntimeException("Language fields must be prefixed lang_. This is a bug."); + } + } + + private LangField compile_field(Object owner, Field field, Function map_name) { + assert_field_prefix(field); + + // Get the annotation + Annotation annotation = null; + for (var a : field.getAnnotations()) { + if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.lang.Lang")) { + if (annotation == null) { + annotation = a; + } else { + throw new RuntimeException("Language fields must have exactly one @Lang annotation."); + } + } + } + assert annotation != null; + final var atype = annotation.annotationType(); + + // Return a correct wrapper object + if (atype.equals(LangMessage.class)) { + return new LangMessageField(module, owner, field, map_name, (LangMessage) annotation); + } else if (atype.equals(LangMessageArray.class)) { + return new LangMessageArrayField(module, owner, field, map_name, (LangMessageArray) annotation); + } else if (atype.equals(LangVersion.class)) { + if (owner != module) { + throw new RuntimeException("@LangVersion can only be used inside the main module. This is a bug."); + } + if (field_version != null) { + throw new RuntimeException( + "There must be exactly one @LangVersion field! (found multiple). This is a bug." + ); + } + return field_version = new LangVersionField(module, owner, field, map_name, (LangVersion) annotation); + } else { + throw new RuntimeException("Missing LangField handler for @" + atype.getName() + ". This is a bug."); + } + } + + private boolean verify_version(File file, long version) { + if (version != expected_version()) { + module.log.severe(file.getName() + ": expected version " + expected_version() + ", but got " + version); + + if (version == 0) { + module.log.severe("Something went wrong while generating or loading the configuration."); + module.log.severe("If you are sure your configuration is correct and this isn't a file"); + module.log.severe( + "system permission issue, please report this to https://github.com/oddlama/vane/issues" + ); + } else if (version < expected_version()) { + module.log.severe("This language file is for an older version of " + module.getName() + "."); + module.log.severe("Please update your file or use an officially supported language file."); + } else { + module.log.severe("This language file is for a future version of " + module.getName() + "."); + module.log.severe("Please use the correct file for this version, or use an officially"); + module.log.severe("supported language file."); + } + + return false; + } + + return true; + } + + @SuppressWarnings("unchecked") + public void compile(Object owner, Function map_name) { + // Compile all annotated fields + lang_fields.addAll( + getAllFields(owner.getClass()) + .stream() + .filter(this::has_lang_annotation) + .map(f -> compile_field(owner, f, map_name)) + .toList() + ); + + if (owner == module && field_version == null) { + throw new RuntimeException("There must be exactly one @LangVersion field! (found none). This is a bug."); + } + } + + @SuppressWarnings("unchecked") + public T get_field(String name) { + var field = lang_fields + .stream() + .filter(f -> f.get_name().equals(name)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Missing lang field lang_" + name)); + + try { + return (T) field; + } catch (ClassCastException e) { + throw new RuntimeException("Invalid lang field type for lang_" + name, e); + } + } + + public boolean reload(File file) { + // Load file + final var yaml = YamlConfiguration.loadConfiguration(file); + + // Check version + final var version = yaml.getLong("version", -1); + if (!verify_version(file, version)) { + return false; + } + + try { + // Check languration for errors + for (var f : lang_fields) { + f.check_loadable(yaml); + } + + for (var f : lang_fields) { + f.load(module.namespace(), yaml); + } + } catch (YamlLoadException e) { + module.log.log(Level.SEVERE, "error while loading '" + file.getAbsolutePath() + "'", e); + return false; + } + return true; + } + + public void generate_resource_pack(final ResourcePackGenerator pack, YamlConfiguration yaml, File lang_file) { + var lang_code = yaml.getString("resource_pack_lang_code"); + if (lang_code == null) { + throw new RuntimeException("Missing yaml key: resource_pack_lang_code"); + } + var errors = new LinkedList(); + for (var f : lang_fields) { + try { + f.add_translations(pack, yaml, lang_code); + } catch (YamlLoadException.Lang e) { + errors.add(e); + } catch (YamlLoadException e) { + module.log.log(Level.SEVERE, "Unexpected YAMLLoadException: ", e); + } + } + if (errors.size() > 0) { + final String errored_lang_nodes = errors + .stream() + .map(Throwable::getMessage) + .collect(Collectors.joining("\n\t\t")); + module.log.log( + Level.SEVERE, + "The following errors were identified while adding translations from \n\t" + + lang_file.getAbsolutePath() + + " \n\t\t" + + errored_lang_nodes + ); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageArrayField.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageArrayField.java index 48044c60d..82d34fa3c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageArrayField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageArrayField.java @@ -6,77 +6,77 @@ import java.util.function.Function; import org.bukkit.configuration.file.YamlConfiguration; import org.oddlama.vane.annotation.lang.LangMessageArray; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.YamlLoadException; import org.oddlama.vane.core.module.Module; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; public class LangMessageArrayField extends LangField { - public LangMessageArray annotation; + public LangMessageArray annotation; - public LangMessageArrayField( - Module module, - Object owner, - Field field, - Function map_name, - LangMessageArray annotation - ) { - super(module, owner, field, map_name); - this.annotation = annotation; - } + public LangMessageArrayField( + Module module, + Object owner, + Field field, + Function map_name, + LangMessageArray annotation + ) { + super(module, owner, field, map_name); + this.annotation = annotation; + } - @Override - public void check_loadable(final YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); + @Override + public void check_loadable(final YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); - if (!yaml.isList(yaml_path())) { - throw new YamlLoadException.Lang("Invalid type for yaml path '" + yaml_path() + "', expected list", this); - } + if (!yaml.isList(yaml_path())) { + throw new YamlLoadException.Lang("Invalid type for yaml path '" + yaml_path() + "', expected list", this); + } - for (final var obj : yaml.getList(yaml_path())) { - if (!(obj instanceof String)) { - throw new YamlLoadException.Lang( - "Invalid type for yaml path '" + yaml_path() + "', expected string", - this - ); - } - } - } + for (final var obj : yaml.getList(yaml_path())) { + if (!(obj instanceof String)) { + throw new YamlLoadException.Lang( + "Invalid type for yaml path '" + yaml_path() + "', expected string", + this + ); + } + } + } - private List from_yaml(final YamlConfiguration yaml) { - final var list = new ArrayList(); - for (final var obj : yaml.getList(yaml_path())) { - list.add((String) obj); - } - return list; - } + private List from_yaml(final YamlConfiguration yaml) { + final var list = new ArrayList(); + for (final var obj : yaml.getList(yaml_path())) { + list.add((String) obj); + } + return list; + } - @Override - public void load(final String namespace, final YamlConfiguration yaml) { - try { - field.set(owner, new TranslatedMessageArray(module(), key(), from_yaml(yaml))); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + @Override + public void load(final String namespace, final YamlConfiguration yaml) { + try { + field.set(owner, new TranslatedMessageArray(module(), key(), from_yaml(yaml))); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } - @Override - public void add_translations(final ResourcePackGenerator pack, final YamlConfiguration yaml, String lang_code) - throws YamlLoadException { - check_loadable(yaml); - final var list = from_yaml(yaml); - final var loaded_size = get().size(); - if (list.size() != loaded_size) { - throw new YamlLoadException.Lang( - "All translation lists for message arrays must have the exact same size. The loaded language file has " + - loaded_size + - " entries, while the currently processed file has " + - list.size(), - this - ); - } - for (int i = 0; i < list.size(); ++i) { - pack.translations(namespace(), lang_code).put(key() + "." + i, list.get(i)); - } - } + @Override + public void add_translations(final ResourcePackGenerator pack, final YamlConfiguration yaml, String lang_code) + throws YamlLoadException { + check_loadable(yaml); + final var list = from_yaml(yaml); + final var loaded_size = get().size(); + if (list.size() != loaded_size) { + throw new YamlLoadException.Lang( + "All translation lists for message arrays must have the exact same size. The loaded language file has " + + loaded_size + + " entries, while the currently processed file has " + + list.size(), + this + ); + } + for (int i = 0; i < list.size(); ++i) { + pack.translations(namespace(), lang_code).put(key() + "." + i, list.get(i)); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageField.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageField.java index f69ba8df1..f771ea397 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangMessageField.java @@ -4,51 +4,51 @@ import java.util.function.Function; import org.bukkit.configuration.file.YamlConfiguration; import org.oddlama.vane.annotation.lang.LangMessage; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.YamlLoadException; import org.oddlama.vane.core.module.Module; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; public class LangMessageField extends LangField { - public LangMessage annotation; - - public LangMessageField( - Module module, - Object owner, - Field field, - Function map_name, - LangMessage annotation - ) { - super(module, owner, field, map_name); - this.annotation = annotation; - } - - @Override - public void check_loadable(final YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!yaml.isString(yaml_path())) { - throw new YamlLoadException.Lang("Invalid type for yaml path '" + yaml_path() + "', expected string", this); - } - } - - private String from_yaml(final YamlConfiguration yaml) { - return yaml.getString(yaml_path()); - } - - @Override - public void load(final String namespace, final YamlConfiguration yaml) { - try { - field.set(owner, new TranslatedMessage(module(), key(), from_yaml(yaml))); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } - - @Override - public void add_translations(final ResourcePackGenerator pack, final YamlConfiguration yaml, String lang_code) - throws YamlLoadException { - check_loadable(yaml); - pack.translations(namespace(), lang_code).put(key(), from_yaml(yaml)); - } + public LangMessage annotation; + + public LangMessageField( + Module module, + Object owner, + Field field, + Function map_name, + LangMessage annotation + ) { + super(module, owner, field, map_name); + this.annotation = annotation; + } + + @Override + public void check_loadable(final YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!yaml.isString(yaml_path())) { + throw new YamlLoadException.Lang("Invalid type for yaml path '" + yaml_path() + "', expected string", this); + } + } + + private String from_yaml(final YamlConfiguration yaml) { + return yaml.getString(yaml_path()); + } + + @Override + public void load(final String namespace, final YamlConfiguration yaml) { + try { + field.set(owner, new TranslatedMessage(module(), key(), from_yaml(yaml))); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } + + @Override + public void add_translations(final ResourcePackGenerator pack, final YamlConfiguration yaml, String lang_code) + throws YamlLoadException { + check_loadable(yaml); + pack.translations(namespace(), lang_code).put(key(), from_yaml(yaml)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangVersionField.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangVersionField.java index ac0856eaf..84afb7ff0 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/LangVersionField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/LangVersionField.java @@ -4,52 +4,52 @@ import java.util.function.Function; import org.bukkit.configuration.file.YamlConfiguration; import org.oddlama.vane.annotation.lang.LangVersion; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.YamlLoadException; import org.oddlama.vane.core.module.Module; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; public class LangVersionField extends LangField { - public LangVersion annotation; - - public LangVersionField( - Module module, - Object owner, - Field field, - Function map_name, - LangVersion annotation - ) { - super(module, owner, field, map_name); - this.annotation = annotation; - } - - @Override - public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { - check_yaml_path(yaml); - - if (!(yaml.get(yaml_path()) instanceof Number)) { - throw new YamlLoadException.Lang("Invalid type for yaml path '" + yaml_path() + "', expected long", this); - } - - var val = yaml.getLong(yaml_path()); - if (val < 1) { - throw new YamlLoadException.Lang( - "Entry '" + yaml_path() + "' has an invalid value: Value must be >= 1", - this - ); - } - } - - @Override - public void load(final String namespace, final YamlConfiguration yaml) { - try { - field.setLong(owner, yaml.getLong(yaml_path())); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } - - @Override - public void add_translations(final ResourcePackGenerator pack, final YamlConfiguration yaml, String lang_code) - throws YamlLoadException {} + public LangVersion annotation; + + public LangVersionField( + Module module, + Object owner, + Field field, + Function map_name, + LangVersion annotation + ) { + super(module, owner, field, map_name); + this.annotation = annotation; + } + + @Override + public void check_loadable(YamlConfiguration yaml) throws YamlLoadException { + check_yaml_path(yaml); + + if (!(yaml.get(yaml_path()) instanceof Number)) { + throw new YamlLoadException.Lang("Invalid type for yaml path '" + yaml_path() + "', expected long", this); + } + + var val = yaml.getLong(yaml_path()); + if (val < 1) { + throw new YamlLoadException.Lang( + "Entry '" + yaml_path() + "' has an invalid value: Value must be >= 1", + this + ); + } + } + + @Override + public void load(final String namespace, final YamlConfiguration yaml) { + try { + field.setLong(owner, yaml.getLong(yaml_path())); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } + + @Override + public void add_translations(final ResourcePackGenerator pack, final YamlConfiguration yaml, String lang_code) + throws YamlLoadException {} } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessage.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessage.java index 87ed49259..f5cab4051 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessage.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessage.java @@ -11,111 +11,114 @@ public class TranslatedMessage { - private Module module; - private String key; - private String default_translation; - - public TranslatedMessage(final Module module, final String key, final String default_translation) { - this.module = module; - this.key = key; - this.default_translation = default_translation; - } - - public String key() { - return key; - } - - public String str(Object... args) { - try { - final var args_as_strings = new Object[args.length]; - for (int i = 0; i < args.length; ++i) { - if (args[i] instanceof Component) { - args_as_strings[i] = LegacyComponentSerializer.legacySection().serialize((Component) args[i]); - } else if (args[i] instanceof String) { - args_as_strings[i] = args[i]; - } else { - throw new RuntimeException( - "Error while formatting message '" + key() + "', invalid argument to str() serializer: " + args[i] - ); - } - } - return String.format(default_translation, args_as_strings); - } catch (Exception e) { - throw new RuntimeException("Error while formatting message '" + key() + "'", e); - } - } - - public @NotNull Component str_component(Object... args) { - return LegacyComponentSerializer.legacySection().deserialize(str(args)); - } - - public Component format(Object... args) { - if (!module.core.config_client_side_translations) { - return str_component(args); - } - - final var list = new ArrayList(); - for (final var o : args) { - if (o instanceof ComponentLike) { - list.add((ComponentLike) o); - } else if (o instanceof String) { - list.add(LegacyComponentSerializer.legacySection().deserialize((String) o)); - } else { - throw new RuntimeException("Error while formatting message '" + key() + "', got invalid argument " + o); - } - } - return Component.translatable(key, list); - } - - public void broadcast_server_players(Object... args) { - final var component = format(args); - for (var player : module.getServer().getOnlinePlayers()) { - player.sendMessage(component); - } - } - - public void broadcast_server(Object... args) { - final var component = format(args); - for (var player : module.getServer().getOnlinePlayers()) { - player.sendMessage(component); - } - module.clog.info(Component.text("[broadcast] ").append(str_component(args))); - } - - public void broadcast_world(final World world, Object... args) { - final var component = format(args); - for (var player : world.getPlayers()) { - player.sendMessage(component); - } - } - - public void broadcast_world_action_bar(final World world, Object... args) { - final var component = format(args); - for (var player : world.getPlayers()) { - player.sendActionBar(component); - } - } - - public void send(final CommandSender sender, Object... args) { - if (sender == null || sender == module.getServer().getConsoleSender()) { - module.getServer().getConsoleSender().sendMessage(str_component(args)); - } else { - sender.sendMessage(format(args)); - } - } - - public void send_action_bar(final CommandSender sender, Object... args) { - if (sender != null && sender != module.getServer().getConsoleSender()) { - sender.sendActionBar(format(args)); - } - } - - public void send_and_log(final CommandSender sender, Object... args) { - module.clog.info(str_component(args)); - - // Also send it to sender if it's not the console - if (sender != null && sender != module.getServer().getConsoleSender()) { - sender.sendMessage(format(args)); - } - } + private Module module; + private String key; + private String default_translation; + + public TranslatedMessage(final Module module, final String key, final String default_translation) { + this.module = module; + this.key = key; + this.default_translation = default_translation; + } + + public String key() { + return key; + } + + public String str(Object... args) { + try { + final var args_as_strings = new Object[args.length]; + for (int i = 0; i < args.length; ++i) { + if (args[i] instanceof Component) { + args_as_strings[i] = LegacyComponentSerializer.legacySection().serialize((Component) args[i]); + } else if (args[i] instanceof String) { + args_as_strings[i] = args[i]; + } else { + throw new RuntimeException( + "Error while formatting message '" + + key() + + "', invalid argument to str() serializer: " + + args[i] + ); + } + } + return String.format(default_translation, args_as_strings); + } catch (Exception e) { + throw new RuntimeException("Error while formatting message '" + key() + "'", e); + } + } + + public @NotNull Component str_component(Object... args) { + return LegacyComponentSerializer.legacySection().deserialize(str(args)); + } + + public Component format(Object... args) { + if (!module.core.config_client_side_translations) { + return str_component(args); + } + + final var list = new ArrayList(); + for (final var o : args) { + if (o instanceof ComponentLike) { + list.add((ComponentLike) o); + } else if (o instanceof String) { + list.add(LegacyComponentSerializer.legacySection().deserialize((String) o)); + } else { + throw new RuntimeException("Error while formatting message '" + key() + "', got invalid argument " + o); + } + } + return Component.translatable(key, list); + } + + public void broadcast_server_players(Object... args) { + final var component = format(args); + for (var player : module.getServer().getOnlinePlayers()) { + player.sendMessage(component); + } + } + + public void broadcast_server(Object... args) { + final var component = format(args); + for (var player : module.getServer().getOnlinePlayers()) { + player.sendMessage(component); + } + module.clog.info(Component.text("[broadcast] ").append(str_component(args))); + } + + public void broadcast_world(final World world, Object... args) { + final var component = format(args); + for (var player : world.getPlayers()) { + player.sendMessage(component); + } + } + + public void broadcast_world_action_bar(final World world, Object... args) { + final var component = format(args); + for (var player : world.getPlayers()) { + player.sendActionBar(component); + } + } + + public void send(final CommandSender sender, Object... args) { + if (sender == null || sender == module.getServer().getConsoleSender()) { + module.getServer().getConsoleSender().sendMessage(str_component(args)); + } else { + sender.sendMessage(format(args)); + } + } + + public void send_action_bar(final CommandSender sender, Object... args) { + if (sender != null && sender != module.getServer().getConsoleSender()) { + sender.sendActionBar(format(args)); + } + } + + public void send_and_log(final CommandSender sender, Object... args) { + module.clog.info(str_component(args)); + + // Also send it to sender if it's not the console + if (sender != null && sender != module.getServer().getConsoleSender()) { + sender.sendMessage(format(args)); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessageArray.java b/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessageArray.java index 0cf9f5763..ee7b844ad 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessageArray.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/lang/TranslatedMessageArray.java @@ -10,73 +10,76 @@ public class TranslatedMessageArray { - private Module module; - private String key; - private List default_translation; + private Module module; + private String key; + private List default_translation; - public TranslatedMessageArray(final Module module, final String key, final List default_translation) { - this.module = module; - this.key = key; - this.default_translation = default_translation; - } + public TranslatedMessageArray(final Module module, final String key, final List default_translation) { + this.module = module; + this.key = key; + this.default_translation = default_translation; + } - public int size() { - return default_translation.size(); - } + public int size() { + return default_translation.size(); + } - public String key() { - return key; - } + public String key() { + return key; + } - public List str(Object... args) { - try { - final var args_as_strings = new Object[args.length]; - for (int i = 0; i < args.length; ++i) { - if (args[i] instanceof Component) { - args_as_strings[i] = LegacyComponentSerializer.legacySection().serialize((Component) args[i]); - } else if (args[i] instanceof String) { - args_as_strings[i] = args[i]; - } else { - throw new RuntimeException( - "Error while formatting message '" + key() + "', invalid argument to str() serializer: " + args[i] - ); - } - } + public List str(Object... args) { + try { + final var args_as_strings = new Object[args.length]; + for (int i = 0; i < args.length; ++i) { + if (args[i] instanceof Component) { + args_as_strings[i] = LegacyComponentSerializer.legacySection().serialize((Component) args[i]); + } else if (args[i] instanceof String) { + args_as_strings[i] = args[i]; + } else { + throw new RuntimeException( + "Error while formatting message '" + + key() + + "', invalid argument to str() serializer: " + + args[i] + ); + } + } - final var list = new ArrayList(); - for (final var s : default_translation) { - list.add(String.format(s, args_as_strings)); - } - return list; - } catch (Exception e) { - throw new RuntimeException("Error while formatting message '" + key() + "'", e); - } - } + final var list = new ArrayList(); + for (final var s : default_translation) { + list.add(String.format(s, args_as_strings)); + } + return list; + } catch (Exception e) { + throw new RuntimeException("Error while formatting message '" + key() + "'", e); + } + } - public List format(Object... args) { - if (!module.core.config_client_side_translations) { - return str(args) - .stream() - .map(s -> LegacyComponentSerializer.legacySection().deserialize(s)) - .collect(Collectors.toList()); - } + public List format(Object... args) { + if (!module.core.config_client_side_translations) { + return str(args) + .stream() + .map(s -> LegacyComponentSerializer.legacySection().deserialize(s)) + .collect(Collectors.toList()); + } - final var arr = new ArrayList(); - for (int i = 0; i < default_translation.size(); ++i) { - final var list = new ArrayList(); - for (final var o : args) { - if (o instanceof ComponentLike) { - list.add((ComponentLike) o); - } else if (o instanceof String) { - list.add(LegacyComponentSerializer.legacySection().deserialize((String) o)); - } else { - throw new RuntimeException( - "Error while formatting message '" + key() + "', got invalid argument " + o - ); - } - } - arr.add(Component.translatable(key + "." + i, list)); - } - return arr; - } + final var arr = new ArrayList(); + for (int i = 0; i < default_translation.size(); ++i) { + final var list = new ArrayList(); + for (final var o : args) { + if (o instanceof ComponentLike) { + list.add((ComponentLike) o); + } else if (o instanceof String) { + list.add(LegacyComponentSerializer.legacySection().deserialize((String) o)); + } else { + throw new RuntimeException( + "Error while formatting message '" + key() + "', got invalid argument " + o + ); + } + } + arr.add(Component.translatable(key + "." + i, list)); + } + return arr; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/material/ExtendedMaterial.java b/vane-core/src/main/java/org/oddlama/vane/core/material/ExtendedMaterial.java index f7e609c25..4b4495337 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/material/ExtendedMaterial.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/material/ExtendedMaterial.java @@ -10,64 +10,66 @@ public class ExtendedMaterial { - private NamespacedKey key; - private Material material; - private HeadMaterial head_material; + private NamespacedKey key; + private Material material; + private HeadMaterial head_material; - private ExtendedMaterial(final NamespacedKey key) { - this.key = key; - this.material = material_from(key); - if (this.material == null) { - this.head_material = HeadMaterialLibrary.from(key); - } else { - this.head_material = null; - } - } + private ExtendedMaterial(final NamespacedKey key) { + this.key = key; + this.material = material_from(key); + if (this.material == null) { + this.head_material = HeadMaterialLibrary.from(key); + } else { + this.head_material = null; + } + } - public NamespacedKey key() { - return key; - } + public NamespacedKey key() { + return key; + } - public boolean is_simple_material() { - return material != null; - } + public boolean is_simple_material() { + return material != null; + } - public static ExtendedMaterial from(final NamespacedKey key) { - final var mat = new ExtendedMaterial(key); - if (mat.material == null && mat.head_material == null && key.namespace().equals("minecraft")) { - // If no material was found and the key doesn't suggest a custom item, return null. - return null; - } - return mat; - } + public static ExtendedMaterial from(final NamespacedKey key) { + final var mat = new ExtendedMaterial(key); + if (mat.material == null && mat.head_material == null && key.namespace().equals("minecraft")) { + // If no material was found and the key doesn't suggest a custom item, return null. + return null; + } + return mat; + } - public static ExtendedMaterial from(final Material material) { - return from(material.getKey()); - } + public static ExtendedMaterial from(final Material material) { + return from(material.getKey()); + } - public static ExtendedMaterial from(final CustomItem customItem) { - return from(customItem.key()); - } + public static ExtendedMaterial from(final CustomItem customItem) { + return from(customItem.key()); + } - public ItemStack item() { - return item(1); - } + public ItemStack item() { + return item(1); + } - public ItemStack item(int amount) { - if (head_material != null) { - final var item = head_material.item(); - item.setAmount(amount); - return item; - } - if (material != null) { - return new ItemStack(material, amount); - } + public ItemStack item(int amount) { + if (head_material != null) { + final var item = head_material.item(); + item.setAmount(amount); + return item; + } + if (material != null) { + return new ItemStack(material, amount); + } - final var custom_item = Core.instance().item_registry().get(key); - if (custom_item == null) { - throw new IllegalStateException("ExtendedMaterial '" + key + "' is neither a classic material, a head nor a custom item!"); - } + final var custom_item = Core.instance().item_registry().get(key); + if (custom_item == null) { + throw new IllegalStateException( + "ExtendedMaterial '" + key + "' is neither a classic material, a head nor a custom item!" + ); + } - return custom_item.newStack(); - } + return custom_item.newStack(); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterial.java b/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterial.java index 9cf4512cc..379eb318d 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterial.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterial.java @@ -13,63 +13,63 @@ public class HeadMaterial { - private NamespacedKey key; - private String name; - private String category; - private Set tags; - private String base64_texture; + private NamespacedKey key; + private String name; + private String category; + private Set tags; + private String base64_texture; - public HeadMaterial( - final NamespacedKey key, - final String name, - final String category, - final List tags, - final String base64_texture - ) { - this.key = key; - this.name = name; - this.category = category; - this.tags = new HashSet<>(tags); - this.base64_texture = base64_texture; - } + public HeadMaterial( + final NamespacedKey key, + final String name, + final String category, + final List tags, + final String base64_texture + ) { + this.key = key; + this.name = name; + this.category = category; + this.tags = new HashSet<>(tags); + this.base64_texture = base64_texture; + } - public NamespacedKey key() { - return key; - } + public NamespacedKey key() { + return key; + } - public String name() { - return name; - } + public String name() { + return name; + } - public String category() { - return category; - } + public String category() { + return category; + } - public Set tags() { - return tags; - } + public Set tags() { + return tags; + } - public String texture() { - return base64_texture; - } + public String texture() { + return base64_texture; + } - public ItemStack item() { - return skull_with_texture(name, base64_texture); - } + public ItemStack item() { + return skull_with_texture(name, base64_texture); + } - public static HeadMaterial from(final JSONObject json) { - final var id = json.getString("id"); - final var name = json.getString("name"); - final var category = json.getString("category"); - final var texture = json.getString("texture"); + public static HeadMaterial from(final JSONObject json) { + final var id = json.getString("id"); + final var name = json.getString("name"); + final var category = json.getString("category"); + final var texture = json.getString("texture"); - final var tags = new ArrayList(); - final var tags_arr = json.getJSONArray("tags"); - for (int i = 0; i < tags_arr.length(); ++i) { - tags.add(tags_arr.getString(i)); - } + final var tags = new ArrayList(); + final var tags_arr = json.getJSONArray("tags"); + for (int i = 0; i < tags_arr.length(); ++i) { + tags.add(tags_arr.getString(i)); + } - final var key = namespaced_key("vane", category + "_" + id); - return new HeadMaterial(key, name, category, tags, texture); - } + final var key = namespaced_key("vane", category + "_" + id); + return new HeadMaterial(key, name, category, tags, texture); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterialLibrary.java b/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterialLibrary.java index d88c2ee1e..81fe4f68c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterialLibrary.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/material/HeadMaterialLibrary.java @@ -10,42 +10,42 @@ public class HeadMaterialLibrary { - private static final Map registry = new HashMap<>(); - private static final Map> categories = new HashMap<>(); - private static final Map> tags = new HashMap<>(); - private static final Map by_texture = new HashMap<>(); - - public static void load(final String string) { - final var json = new JSONArray(string); - for (int i = 0; i < json.length(); ++i) { - // Deserialize - final var mat = HeadMaterial.from(json.getJSONObject(i)); - - // Add to registry - registry.put(mat.key(), mat); - by_texture.put(mat.texture(), mat); - - // Add to category lookup - var category = categories.computeIfAbsent(mat.category(), k -> new ArrayList<>()); - category.add(mat); - - // Add to tag lookup - for (final var tag : mat.tags()) { - var tag_list = tags.computeIfAbsent(tag, k -> new ArrayList<>()); - tag_list.add(mat); - } - } - } - - public static HeadMaterial from(final NamespacedKey key) { - return registry.get(key); - } - - public static HeadMaterial from_texture(final String base64_texture) { - return by_texture.get(base64_texture); - } - - public static Collection all() { - return registry.values(); - } + private static final Map registry = new HashMap<>(); + private static final Map> categories = new HashMap<>(); + private static final Map> tags = new HashMap<>(); + private static final Map by_texture = new HashMap<>(); + + public static void load(final String string) { + final var json = new JSONArray(string); + for (int i = 0; i < json.length(); ++i) { + // Deserialize + final var mat = HeadMaterial.from(json.getJSONObject(i)); + + // Add to registry + registry.put(mat.key(), mat); + by_texture.put(mat.texture(), mat); + + // Add to category lookup + var category = categories.computeIfAbsent(mat.category(), k -> new ArrayList<>()); + category.add(mat); + + // Add to tag lookup + for (final var tag : mat.tags()) { + var tag_list = tags.computeIfAbsent(tag, k -> new ArrayList<>()); + tag_list.add(mat); + } + } + } + + public static HeadMaterial from(final NamespacedKey key) { + return registry.get(key); + } + + public static HeadMaterial from_texture(final String base64_texture) { + return by_texture.get(base64_texture); + } + + public static Collection all() { + return registry.values(); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/AnvilMenu.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/AnvilMenu.java index c1e6705ee..ed4818626 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/AnvilMenu.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/AnvilMenu.java @@ -13,61 +13,57 @@ public class AnvilMenu extends Menu { - private ServerPlayer entity; - private AnvilContainer container; - private int container_id; - private String title; + private ServerPlayer entity; + private AnvilContainer container; + private int container_id; + private String title; - public AnvilMenu(final Context context, final org.bukkit.entity.Player player, final String title) { - super(context); - this.title = title; - this.entity = player_handle(player); - this.container_id = entity.nextContainerCounter(); - this.container = new AnvilContainer(container_id, entity); - this.container.setTitle(Component.literal(title)); - this.inventory = container.getBukkitView().getTopInventory(); - } + public AnvilMenu(final Context context, final org.bukkit.entity.Player player, final String title) { + super(context); + this.title = title; + this.entity = player_handle(player); + this.container_id = entity.nextContainerCounter(); + this.container = new AnvilContainer(container_id, entity); + this.container.setTitle(Component.literal(title)); + this.inventory = container.getBukkitView().getTopInventory(); + } - @Override - public void open_window(final org.bukkit.entity.Player player) { - if (tainted) { - return; - } + @Override + public void open_window(final org.bukkit.entity.Player player) { + if (tainted) { + return; + } - if (player_handle(player) != entity) { - manager - .get_module() - .log.warning("AnvilMenu.open() was called with a player for whom this inventory wasn't created!"); - } + if (player_handle(player) != entity) { + manager + .get_module() + .log.warning("AnvilMenu.open() was called with a player for whom this inventory wasn't created!"); + } - entity.connection.send( - new ClientboundOpenScreenPacket(container_id, container.getType(), Component.literal(title)) - ); - entity.initMenu(container); - entity.containerMenu = container; - } + entity.connection.send( + new ClientboundOpenScreenPacket(container_id, container.getType(), Component.literal(title)) + ); + entity.initMenu(container); + entity.containerMenu = container; + } - private class AnvilContainer extends net.minecraft.world.inventory.AnvilMenu { + private class AnvilContainer extends net.minecraft.world.inventory.AnvilMenu { - public AnvilContainer(int window_id, final Player entity) { - super( - window_id, - entity.getInventory(), - ContainerLevelAccess.create(entity.level(), new BlockPos(0, 0, 0)) - ); - this.checkReachable = false; - } + public AnvilContainer(int window_id, final Player entity) { + super(window_id, entity.getInventory(), ContainerLevelAccess.create(entity.level(), new BlockPos(0, 0, 0))); + this.checkReachable = false; + } - @Override - public void createResult() { - super.createResult(); - this.cost.set(0); - } + @Override + public void createResult() { + super.createResult(); + this.cost.set(0); + } - @Override - public void removed(Player player) {} + @Override + public void removed(Player player) {} - @Override - protected void clearContainer(Player player, Container container) {} - } + @Override + protected void clearContainer(Player player, Container container) {} + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/Filter.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/Filter.java index 77f18de16..de38ca42c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/Filter.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/Filter.java @@ -10,75 +10,73 @@ import org.oddlama.vane.core.module.Context; public interface Filter { - public void open_filter_settings( - final Context context, - final Player player, - final String filter_title, - final Menu return_to - ); + public void open_filter_settings( + final Context context, + final Player player, + final String filter_title, + final Menu return_to + ); - public void reset(); + public void reset(); - public List filter(final List things); + public List filter(final List things); - public static class StringFilter implements Filter { + public static class StringFilter implements Filter { - private String str = null; - private Function2 do_filter; - private boolean ignore_case; + private String str = null; + private Function2 do_filter; + private boolean ignore_case; - public StringFilter(final Function2 do_filter) { - this(do_filter, true); - } + public StringFilter(final Function2 do_filter) { + this(do_filter, true); + } - public StringFilter(final Function2 do_filter, boolean ignore_case) { - this.do_filter = do_filter; - this.ignore_case = ignore_case; - } + public StringFilter(final Function2 do_filter, boolean ignore_case) { + this.do_filter = do_filter; + this.ignore_case = ignore_case; + } - @Override - public void open_filter_settings( - final Context context, - final Player player, - final String filter_title, - final Menu return_to - ) { - MenuFactory - .anvil_string_input( - context, - player, - filter_title, - new ItemStack(Material.PAPER), - "?", - (p, menu, s) -> { - menu.close(p); - str = s; - return_to.open(p); - return ClickResult.SUCCESS; - } - ) - .open(player); - } + @Override + public void open_filter_settings( + final Context context, + final Player player, + final String filter_title, + final Menu return_to + ) { + MenuFactory.anvil_string_input( + context, + player, + filter_title, + new ItemStack(Material.PAPER), + "?", + (p, menu, s) -> { + menu.close(p); + str = s; + return_to.open(p); + return ClickResult.SUCCESS; + } + ).open(player); + } - @Override - public void reset() { - str = null; - } + @Override + public void reset() { + str = null; + } - @Override - public List filter(final List things) { - if (str == null) { - return things; - } else { - final String f_str; - if (ignore_case) { - f_str = str.toLowerCase(); - } else { - f_str = str; - } + @Override + public List filter(final List things) { + if (str == null) { + return things; + } else { + final String f_str; + if (ignore_case) { + f_str = str.toLowerCase(); + } else { + f_str = str; + } - return things.stream().filter(t -> do_filter.apply(t, f_str)).collect(Collectors.toList()); - } - } - } + return things.stream().filter(t -> do_filter.apply(t, f_str)).collect(Collectors.toList()); + } + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/GenericSelector.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/GenericSelector.java index 89716a633..4266fb06f 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/GenericSelector.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/GenericSelector.java @@ -15,261 +15,259 @@ public class GenericSelector> { - private MenuManager menu_manager; - private Function1 to_item; - private Function4 on_click; - - private List things; - private F filter; - private int page_size; - - private boolean update_filter = true; - private int page = 0; - private int last_page = 0; - private List filtered_things = null; - - private GenericSelector() {} - - public static > Menu create( - final Context context, - final Player player, - final String title, - final String filter_title, - final List things, - final Function1 to_item, - final F filter, - final Function4 on_click, - final Consumer1 on_cancel - ) { - final var columns = 9; - - final var generic_selector = new GenericSelector(); - generic_selector.menu_manager = context.get_module().core.menu_manager; - generic_selector.to_item = to_item; - generic_selector.on_click = on_click; - generic_selector.things = things; - generic_selector.filter = filter; - generic_selector.page_size = 5 * columns; - - final var generic_selector_menu = new Menu( - context, - Bukkit.createInventory(null, 6 * columns, LegacyComponentSerializer.legacySection().deserialize(title)) - ) { - @Override - public void update(boolean force_update) { - if (generic_selector.update_filter) { - // Filter list before update - generic_selector.filtered_things = generic_selector.filter.filter(generic_selector.things); - generic_selector.page = 0; - generic_selector.last_page = - Math.max(0, generic_selector.filtered_things.size() - 1) / generic_selector.page_size; - generic_selector.update_filter = false; - } - super.update(force_update); - } - }; - - // Selection area - generic_selector_menu.add(new SelectionArea<>(generic_selector, 0)); - - // Page selector - generic_selector_menu.add( - new PageSelector<>(generic_selector, generic_selector.page_size + 1, generic_selector.page_size + 8) - ); - - // Filter item - generic_selector_menu.add( - new MenuItem( - generic_selector.page_size, - generic_selector.menu_manager.generic_selector_filter.item(), - (p, menu, self, event) -> { - if (!Menu.is_left_or_right_click(event)) { - return ClickResult.INVALID_CLICK; - } - - if (event.getClick() == ClickType.RIGHT) { - generic_selector.filter.reset(); - generic_selector.update_filter = true; - menu.update(); - } else { - menu.close(p); - generic_selector.filter.open_filter_settings(context, p, filter_title, menu); - generic_selector.update_filter = true; - } - return ClickResult.SUCCESS; - } - ) - ); - - // Cancel item - generic_selector_menu.add( - new MenuItem( - generic_selector.page_size + 8, - generic_selector.menu_manager.generic_selector_cancel.item(), - (p, menu, self) -> { - menu.close(p); - on_cancel.apply(player); - return ClickResult.SUCCESS; - } - ) - ); - - // On natural close call cancel - generic_selector_menu.on_natural_close(on_cancel); - - return generic_selector_menu; - } - - public static class PageSelector> implements MenuWidget { - - private static final int BIG_JUMP_SIZE = 5; - - private final GenericSelector generic_selector; - private final int slot_from; // Inclusive - private final int slot_to; // Exclusive - - // Shows page selector from [from, too) - public PageSelector(final GenericSelector generic_selector, int slot_from, int slot_to) { - this.generic_selector = generic_selector; - this.slot_from = slot_from; - this.slot_to = slot_to; - if (this.slot_to - this.slot_from < 3) { - throw new IllegalArgumentException("PageSelector needs at least 3 assigned slots!"); - } - if (((this.slot_to - this.slot_from) % 2) == 0) { - throw new IllegalArgumentException("PageSelector needs an uneven number of assigned slots!"); - } - } - - @Override - public boolean update(final Menu menu) { - for (int slot = slot_from; slot < slot_to; ++slot) { - final var i = slot - slot_from; - final var offset = button_offset(i); - final var page = page_for_offset(offset); - final var no_op = page == generic_selector.page; - final var actual_offset = page - generic_selector.page; - final ItemStack item; - if (i == (slot_to - slot_from) / 2) { - // Current page indicator - item = - generic_selector.menu_manager.generic_selector_current_page.item( - "§6" + (page + 1), - "§6" + (generic_selector.last_page + 1), - "§6" + generic_selector.filtered_things.size() - ); - } else if (no_op) { - item = null; - } else { - item = - generic_selector.menu_manager.generic_selector_page.item_amount( - Math.abs(actual_offset), - "§6" + (page + 1) - ); - } - - menu.inventory().setItem(slot, item); - } - return true; - } - - private int button_offset(int i) { - if (i <= 0) { - // Go back up to BIG_JUMP_SIZE pages - return -BIG_JUMP_SIZE; - } else if (i >= (slot_to - slot_from) - 1) { - // Go forward up to BIG_JUMP_SIZE pages - return BIG_JUMP_SIZE; - } else { - final var base = (slot_to - slot_from) / 2; - return i - base; - } - } - - private int page_for_offset(int offset) { - int page = generic_selector.page + offset; - if (page < 0) { - page = 0; - } else if (page > generic_selector.last_page) { - page = generic_selector.last_page; - } - return page; - } - - @Override - public ClickResult click( - final Player player, - final Menu menu, - final ItemStack item, - int slot, - final InventoryClickEvent event - ) { - if (slot < slot_from || slot >= slot_to) { - return ClickResult.IGNORE; - } - - if (menu.inventory().getItem(slot) == null) { - return ClickResult.IGNORE; - } - - if (!Menu.is_left_click(event)) { - return ClickResult.INVALID_CLICK; - } - - final var offset = button_offset(slot - slot_from); - generic_selector.page = page_for_offset(offset); - - menu.update(); - return ClickResult.SUCCESS; - } - } - - public static class SelectionArea> implements MenuWidget { - - private final GenericSelector generic_selector; - private final int first_slot; - - public SelectionArea(final GenericSelector generic_selector, final int first_slot) { - this.generic_selector = generic_selector; - this.first_slot = first_slot; - } - - @Override - public boolean update(final Menu menu) { - for (int i = 0; i < generic_selector.page_size; ++i) { - final var idx = generic_selector.page * generic_selector.page_size + i; - if (idx >= generic_selector.filtered_things.size()) { - menu.inventory().setItem(first_slot + i, null); - } else { - menu - .inventory() - .setItem( - first_slot + i, - generic_selector.to_item.apply(generic_selector.filtered_things.get(idx)) - ); - } - } - return true; - } - - @Override - public ClickResult click( - final Player player, - final Menu menu, - final ItemStack item, - int slot, - final InventoryClickEvent event - ) { - if (slot < first_slot || slot >= first_slot + generic_selector.page_size) { - return ClickResult.IGNORE; - } - - if (menu.inventory().getItem(slot) == null) { - return ClickResult.IGNORE; - } - - final var idx = generic_selector.page * generic_selector.page_size + (slot - first_slot); - return generic_selector.on_click.apply(player, menu, generic_selector.filtered_things.get(idx), event); - } - } + private MenuManager menu_manager; + private Function1 to_item; + private Function4 on_click; + + private List things; + private F filter; + private int page_size; + + private boolean update_filter = true; + private int page = 0; + private int last_page = 0; + private List filtered_things = null; + + private GenericSelector() {} + + public static > Menu create( + final Context context, + final Player player, + final String title, + final String filter_title, + final List things, + final Function1 to_item, + final F filter, + final Function4 on_click, + final Consumer1 on_cancel + ) { + final var columns = 9; + + final var generic_selector = new GenericSelector(); + generic_selector.menu_manager = context.get_module().core.menu_manager; + generic_selector.to_item = to_item; + generic_selector.on_click = on_click; + generic_selector.things = things; + generic_selector.filter = filter; + generic_selector.page_size = 5 * columns; + + final var generic_selector_menu = new Menu( + context, + Bukkit.createInventory(null, 6 * columns, LegacyComponentSerializer.legacySection().deserialize(title)) + ) { + @Override + public void update(boolean force_update) { + if (generic_selector.update_filter) { + // Filter list before update + generic_selector.filtered_things = generic_selector.filter.filter(generic_selector.things); + generic_selector.page = 0; + generic_selector.last_page = + Math.max(0, generic_selector.filtered_things.size() - 1) / generic_selector.page_size; + generic_selector.update_filter = false; + } + super.update(force_update); + } + }; + + // Selection area + generic_selector_menu.add(new SelectionArea<>(generic_selector, 0)); + + // Page selector + generic_selector_menu.add( + new PageSelector<>(generic_selector, generic_selector.page_size + 1, generic_selector.page_size + 8) + ); + + // Filter item + generic_selector_menu.add( + new MenuItem( + generic_selector.page_size, + generic_selector.menu_manager.generic_selector_filter.item(), + (p, menu, self, event) -> { + if (!Menu.is_left_or_right_click(event)) { + return ClickResult.INVALID_CLICK; + } + + if (event.getClick() == ClickType.RIGHT) { + generic_selector.filter.reset(); + generic_selector.update_filter = true; + menu.update(); + } else { + menu.close(p); + generic_selector.filter.open_filter_settings(context, p, filter_title, menu); + generic_selector.update_filter = true; + } + return ClickResult.SUCCESS; + } + ) + ); + + // Cancel item + generic_selector_menu.add( + new MenuItem( + generic_selector.page_size + 8, + generic_selector.menu_manager.generic_selector_cancel.item(), + (p, menu, self) -> { + menu.close(p); + on_cancel.apply(player); + return ClickResult.SUCCESS; + } + ) + ); + + // On natural close call cancel + generic_selector_menu.on_natural_close(on_cancel); + + return generic_selector_menu; + } + + public static class PageSelector> implements MenuWidget { + + private static final int BIG_JUMP_SIZE = 5; + + private final GenericSelector generic_selector; + private final int slot_from; // Inclusive + private final int slot_to; // Exclusive + + // Shows page selector from [from, too) + public PageSelector(final GenericSelector generic_selector, int slot_from, int slot_to) { + this.generic_selector = generic_selector; + this.slot_from = slot_from; + this.slot_to = slot_to; + if (this.slot_to - this.slot_from < 3) { + throw new IllegalArgumentException("PageSelector needs at least 3 assigned slots!"); + } + if (((this.slot_to - this.slot_from) % 2) == 0) { + throw new IllegalArgumentException("PageSelector needs an uneven number of assigned slots!"); + } + } + + @Override + public boolean update(final Menu menu) { + for (int slot = slot_from; slot < slot_to; ++slot) { + final var i = slot - slot_from; + final var offset = button_offset(i); + final var page = page_for_offset(offset); + final var no_op = page == generic_selector.page; + final var actual_offset = page - generic_selector.page; + final ItemStack item; + if (i == (slot_to - slot_from) / 2) { + // Current page indicator + item = generic_selector.menu_manager.generic_selector_current_page.item( + "§6" + (page + 1), + "§6" + (generic_selector.last_page + 1), + "§6" + generic_selector.filtered_things.size() + ); + } else if (no_op) { + item = null; + } else { + item = generic_selector.menu_manager.generic_selector_page.item_amount( + Math.abs(actual_offset), + "§6" + (page + 1) + ); + } + + menu.inventory().setItem(slot, item); + } + return true; + } + + private int button_offset(int i) { + if (i <= 0) { + // Go back up to BIG_JUMP_SIZE pages + return -BIG_JUMP_SIZE; + } else if (i >= (slot_to - slot_from) - 1) { + // Go forward up to BIG_JUMP_SIZE pages + return BIG_JUMP_SIZE; + } else { + final var base = (slot_to - slot_from) / 2; + return i - base; + } + } + + private int page_for_offset(int offset) { + int page = generic_selector.page + offset; + if (page < 0) { + page = 0; + } else if (page > generic_selector.last_page) { + page = generic_selector.last_page; + } + return page; + } + + @Override + public ClickResult click( + final Player player, + final Menu menu, + final ItemStack item, + int slot, + final InventoryClickEvent event + ) { + if (slot < slot_from || slot >= slot_to) { + return ClickResult.IGNORE; + } + + if (menu.inventory().getItem(slot) == null) { + return ClickResult.IGNORE; + } + + if (!Menu.is_left_click(event)) { + return ClickResult.INVALID_CLICK; + } + + final var offset = button_offset(slot - slot_from); + generic_selector.page = page_for_offset(offset); + + menu.update(); + return ClickResult.SUCCESS; + } + } + + public static class SelectionArea> implements MenuWidget { + + private final GenericSelector generic_selector; + private final int first_slot; + + public SelectionArea(final GenericSelector generic_selector, final int first_slot) { + this.generic_selector = generic_selector; + this.first_slot = first_slot; + } + + @Override + public boolean update(final Menu menu) { + for (int i = 0; i < generic_selector.page_size; ++i) { + final var idx = generic_selector.page * generic_selector.page_size + i; + if (idx >= generic_selector.filtered_things.size()) { + menu.inventory().setItem(first_slot + i, null); + } else { + menu + .inventory() + .setItem( + first_slot + i, + generic_selector.to_item.apply(generic_selector.filtered_things.get(idx)) + ); + } + } + return true; + } + + @Override + public ClickResult click( + final Player player, + final Menu menu, + final ItemStack item, + int slot, + final InventoryClickEvent event + ) { + if (slot < first_slot || slot >= first_slot + generic_selector.page_size) { + return ClickResult.IGNORE; + } + + if (menu.inventory().getItem(slot) == null) { + return ClickResult.IGNORE; + } + + final var idx = generic_selector.page * generic_selector.page_size + (slot - first_slot); + return generic_selector.on_click.apply(player, menu, generic_selector.filtered_things.get(idx), event); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadFilter.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadFilter.java index f4b449fb2..4ed76349f 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadFilter.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadFilter.java @@ -11,66 +11,64 @@ public class HeadFilter implements Filter { - private String str = null; + private String str = null; - public HeadFilter() {} + public HeadFilter() {} - @Override - public void open_filter_settings( - final Context context, - final Player player, - final String filter_title, - final Menu return_to - ) { - MenuFactory - .anvil_string_input( - context, - player, - filter_title, - new ItemStack(Material.PAPER), - "?", - (p, menu, s) -> { - menu.close(p); - str = s.toLowerCase(); - return_to.open(p); - return ClickResult.SUCCESS; - } - ) - .open(player); - } + @Override + public void open_filter_settings( + final Context context, + final Player player, + final String filter_title, + final Menu return_to + ) { + MenuFactory.anvil_string_input( + context, + player, + filter_title, + new ItemStack(Material.PAPER), + "?", + (p, menu, s) -> { + menu.close(p); + str = s.toLowerCase(); + return_to.open(p); + return ClickResult.SUCCESS; + } + ).open(player); + } - @Override - public void reset() { - str = null; - } + @Override + public void reset() { + str = null; + } - private boolean filter_by_categories(final HeadMaterial material) { - return material.category().toLowerCase().contains(str); - } + private boolean filter_by_categories(final HeadMaterial material) { + return material.category().toLowerCase().contains(str); + } - private boolean filter_by_tags(final HeadMaterial material) { - for (final var tag : material.tags()) { - if (tag.toLowerCase().contains(str)) { - return true; - } - } + private boolean filter_by_tags(final HeadMaterial material) { + for (final var tag : material.tags()) { + if (tag.toLowerCase().contains(str)) { + return true; + } + } - return false; - } + return false; + } - private boolean filter_by_name(final HeadMaterial material) { - return material.name().toLowerCase().contains(str); - } + private boolean filter_by_name(final HeadMaterial material) { + return material.name().toLowerCase().contains(str); + } - @Override - public List filter(final List things) { - if (str == null) { - return things; - } + @Override + public List filter(final List things) { + if (str == null) { + return things; + } - return things - .stream() - .filter(t -> filter_by_categories(t) || filter_by_tags(t) || filter_by_name(t)) - .collect(Collectors.toList()); - } + return things + .stream() + .filter(t -> filter_by_categories(t) || filter_by_tags(t) || filter_by_name(t)) + .collect(Collectors.toList()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadSelectorGroup.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadSelectorGroup.java index 71cea34fc..194facaeb 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadSelectorGroup.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/HeadSelectorGroup.java @@ -10,29 +10,28 @@ public class HeadSelectorGroup extends ModuleComponent { - @LangMessage - public TranslatedMessage lang_title; - - @LangMessage - public TranslatedMessage lang_filter_title; - - public TranslatedItemStack item_select_head; - - public HeadSelectorGroup(Context context) { - super(context.namespace("head_selector", "Menu configuration for the head selector menu.")); - item_select_head = - new TranslatedItemStack<>( - get_context(), - "select_head", - Material.BARRIER, - 1, - "Used to represent a head in the head library." - ); - } - - @Override - public void on_enable() {} - - @Override - public void on_disable() {} + @LangMessage + public TranslatedMessage lang_title; + + @LangMessage + public TranslatedMessage lang_filter_title; + + public TranslatedItemStack item_select_head; + + public HeadSelectorGroup(Context context) { + super(context.namespace("head_selector", "Menu configuration for the head selector menu.")); + item_select_head = new TranslatedItemStack<>( + get_context(), + "select_head", + Material.BARRIER, + 1, + "Used to represent a head in the head library." + ); + } + + @Override + public void on_enable() {} + + @Override + public void on_disable() {} } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/Menu.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/Menu.java index 569e66b3c..5ae375606 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/Menu.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/Menu.java @@ -18,199 +18,199 @@ public class Menu { - protected final MenuManager manager; - protected Inventory inventory = null; - private final Set widgets = new HashSet<>(); - private Consumer2 on_close = null; - private Consumer1 on_natural_close = null; - private Object tag = null; - - // A tainted menu will refuse to be opened. - // Useful to prevent an invalid menu from reopening - // after its state has been captured. - protected boolean tainted = false; - - protected Menu(final Context context) { - this.manager = context.get_module().core.menu_manager; - } - - public Menu(final Context context, final Inventory inventory) { - this.manager = context.get_module().core.menu_manager; - this.inventory = inventory; - } - - public MenuManager manager() { - return manager; - } - - public Inventory inventory() { - return inventory; - } - - public Object tag() { - return tag; - } - - public Menu tag(Object tag) { - this.tag = tag; - return this; - } - - public void taint() { - this.tainted = true; - } - - public void add(final MenuWidget widget) { - widgets.add(widget); - } - - public boolean remove(final MenuWidget widget) { - return widgets.remove(widget); - } - - public void update() { - update(false); - } - - public void update(boolean force_update) { - int updated = widgets.stream().mapToInt(w -> w.update(this) ? 1 : 0).sum(); - - if (updated > 0 || force_update) { - // Send inventory content to players - manager.update(this); - } - } - - public void open_window(final Player player) { - if (tainted) { - return; - } - player.openInventory(inventory); - } - - public final void open(final Player player) { - if (tainted) { - return; - } - update(true); - manager.schedule_next_tick(() -> { - manager.add(player, this); - open_window(player); - }); - } - - public boolean close(final Player player) { - return close(player, InventoryCloseEvent.Reason.PLUGIN); - } - - public boolean close(final Player player, final InventoryCloseEvent.Reason reason) { - final var top_inventory = player.getOpenInventory().getTopInventory(); - if (top_inventory != inventory) { - try { - throw new RuntimeException("Invalid close from unrelated menu."); - } catch (RuntimeException e) { - manager - .get_module() - .log.log( - Level.WARNING, - "Tried to close menu inventory that isn't opened by the player " + player, - e - ); - } - return false; - } - - manager.schedule_next_tick(() -> player.closeInventory(reason)); - return true; - } - - public Consumer2 get_on_close() { - return on_close; - } - - public Menu on_close(final Consumer2 on_close) { - this.on_close = on_close; - return this; - } - - public Consumer1 get_on_natural_close() { - return on_natural_close; - } - - public Menu on_natural_close(final Consumer1 on_natural_close) { - this.on_natural_close = on_natural_close; - return this; - } - - public final void closed(final Player player, final InventoryCloseEvent.Reason reason) { - if (reason == InventoryCloseEvent.Reason.PLAYER && on_natural_close != null) { - on_natural_close.apply(player); - } else { - if (on_close != null) { - on_close.apply(player, reason); - } - } - inventory.clear(); - manager.remove(player, this); - } - - public ClickResult on_click(final Player player, final ItemStack item, int slot, final InventoryClickEvent event) { - return ClickResult.IGNORE; - } - - public final void click(final Player player, final ItemStack item, int slot, final InventoryClickEvent event) { - // Ignore unknown click actions - if (event.getAction() == InventoryAction.UNKNOWN) { - return; - } - - // Send event to this menu - var result = ClickResult.IGNORE; - result = ClickResult.or(result, on_click(player, item, slot, event)); - - // Send event to all widgets - for (final var widget : widgets) { - result = ClickResult.or(result, widget.click(player, this, item, slot, event)); - } - - switch (result) { - default: - case INVALID_CLICK: - case IGNORE: - break; - case SUCCESS: - player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, SoundCategory.MASTER, 1.0f, 1.0f); - break; - case ERROR: - player.playSound(player.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, SoundCategory.MASTER, 1.0f, 1.0f); - break; - } - } - - public static boolean is_left_or_right_click(final InventoryClickEvent event) { - final var type = event.getClick(); - return type == ClickType.LEFT || type == ClickType.RIGHT; - } - - public static boolean is_left_click(final InventoryClickEvent event) { - final var type = event.getClick(); - return type == ClickType.LEFT; - } - - public static enum ClickResult { - IGNORE(0), - INVALID_CLICK(1), - SUCCESS(2), - ERROR(3); - - private int priority; - - private ClickResult(int priority) { - this.priority = priority; - } - - public static ClickResult or(final ClickResult a, final ClickResult b) { - return a.priority > b.priority ? a : b; - } - } + protected final MenuManager manager; + protected Inventory inventory = null; + private final Set widgets = new HashSet<>(); + private Consumer2 on_close = null; + private Consumer1 on_natural_close = null; + private Object tag = null; + + // A tainted menu will refuse to be opened. + // Useful to prevent an invalid menu from reopening + // after its state has been captured. + protected boolean tainted = false; + + protected Menu(final Context context) { + this.manager = context.get_module().core.menu_manager; + } + + public Menu(final Context context, final Inventory inventory) { + this.manager = context.get_module().core.menu_manager; + this.inventory = inventory; + } + + public MenuManager manager() { + return manager; + } + + public Inventory inventory() { + return inventory; + } + + public Object tag() { + return tag; + } + + public Menu tag(Object tag) { + this.tag = tag; + return this; + } + + public void taint() { + this.tainted = true; + } + + public void add(final MenuWidget widget) { + widgets.add(widget); + } + + public boolean remove(final MenuWidget widget) { + return widgets.remove(widget); + } + + public void update() { + update(false); + } + + public void update(boolean force_update) { + int updated = widgets.stream().mapToInt(w -> w.update(this) ? 1 : 0).sum(); + + if (updated > 0 || force_update) { + // Send inventory content to players + manager.update(this); + } + } + + public void open_window(final Player player) { + if (tainted) { + return; + } + player.openInventory(inventory); + } + + public final void open(final Player player) { + if (tainted) { + return; + } + update(true); + manager.schedule_next_tick(() -> { + manager.add(player, this); + open_window(player); + }); + } + + public boolean close(final Player player) { + return close(player, InventoryCloseEvent.Reason.PLUGIN); + } + + public boolean close(final Player player, final InventoryCloseEvent.Reason reason) { + final var top_inventory = player.getOpenInventory().getTopInventory(); + if (top_inventory != inventory) { + try { + throw new RuntimeException("Invalid close from unrelated menu."); + } catch (RuntimeException e) { + manager + .get_module() + .log.log( + Level.WARNING, + "Tried to close menu inventory that isn't opened by the player " + player, + e + ); + } + return false; + } + + manager.schedule_next_tick(() -> player.closeInventory(reason)); + return true; + } + + public Consumer2 get_on_close() { + return on_close; + } + + public Menu on_close(final Consumer2 on_close) { + this.on_close = on_close; + return this; + } + + public Consumer1 get_on_natural_close() { + return on_natural_close; + } + + public Menu on_natural_close(final Consumer1 on_natural_close) { + this.on_natural_close = on_natural_close; + return this; + } + + public final void closed(final Player player, final InventoryCloseEvent.Reason reason) { + if (reason == InventoryCloseEvent.Reason.PLAYER && on_natural_close != null) { + on_natural_close.apply(player); + } else { + if (on_close != null) { + on_close.apply(player, reason); + } + } + inventory.clear(); + manager.remove(player, this); + } + + public ClickResult on_click(final Player player, final ItemStack item, int slot, final InventoryClickEvent event) { + return ClickResult.IGNORE; + } + + public final void click(final Player player, final ItemStack item, int slot, final InventoryClickEvent event) { + // Ignore unknown click actions + if (event.getAction() == InventoryAction.UNKNOWN) { + return; + } + + // Send event to this menu + var result = ClickResult.IGNORE; + result = ClickResult.or(result, on_click(player, item, slot, event)); + + // Send event to all widgets + for (final var widget : widgets) { + result = ClickResult.or(result, widget.click(player, this, item, slot, event)); + } + + switch (result) { + default: + case INVALID_CLICK: + case IGNORE: + break; + case SUCCESS: + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, SoundCategory.MASTER, 1.0f, 1.0f); + break; + case ERROR: + player.playSound(player.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, SoundCategory.MASTER, 1.0f, 1.0f); + break; + } + } + + public static boolean is_left_or_right_click(final InventoryClickEvent event) { + final var type = event.getClick(); + return type == ClickType.LEFT || type == ClickType.RIGHT; + } + + public static boolean is_left_click(final InventoryClickEvent event) { + final var type = event.getClick(); + return type == ClickType.LEFT; + } + + public static enum ClickResult { + IGNORE(0), + INVALID_CLICK(1), + SUCCESS(2), + ERROR(3); + + private int priority; + + private ClickResult(int priority) { + this.priority = priority; + } + + public static ClickResult or(final ClickResult a, final ClickResult b) { + return a.priority > b.priority ? a : b; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuFactory.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuFactory.java index d5ff49f1d..a2eab19d3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuFactory.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuFactory.java @@ -25,318 +25,290 @@ public class MenuFactory { - public static Menu anvil_string_input( - final Context context, - final Player player, - final String title, - final ItemStack _input_item, - final String default_name, - final Function3 on_click - ) { - final var input_item = _input_item.clone(); - final var meta = input_item.getItemMeta(); - meta.displayName(LegacyComponentSerializer.legacySection().deserialize(default_name)); - input_item.setItemMeta(meta); + public static Menu anvil_string_input( + final Context context, + final Player player, + final String title, + final ItemStack _input_item, + final String default_name, + final Function3 on_click + ) { + final var input_item = _input_item.clone(); + final var meta = input_item.getItemMeta(); + meta.displayName(LegacyComponentSerializer.legacySection().deserialize(default_name)); + input_item.setItemMeta(meta); - final var anvil = new AnvilMenu(context, player, title); - anvil.add(new MenuItem(0, input_item)); - anvil.add( - new MenuItemClickListener( - 2, - (p, menu, item) -> on_click.apply(p, menu, name_of(item)) - ) - ); - return anvil; - } + final var anvil = new AnvilMenu(context, player, title); + anvil.add(new MenuItem(0, input_item)); + anvil.add(new MenuItemClickListener(2, (p, menu, item) -> on_click.apply(p, menu, name_of(item)))); + return anvil; + } - public static Menu confirm( - final Context context, - final String title, - final ItemStack item_confirm, - final Function1 on_confirm, - final ItemStack item_cancel, - final Consumer1 on_cancel - ) { - final var columns = 9; - final var confirmation_menu = new Menu( - context, - Bukkit.createInventory(null, columns, LegacyComponentSerializer.legacySection().deserialize(title)) - ); - final var confirm_index = (int) (Math.random() * columns); + public static Menu confirm( + final Context context, + final String title, + final ItemStack item_confirm, + final Function1 on_confirm, + final ItemStack item_cancel, + final Consumer1 on_cancel + ) { + final var columns = 9; + final var confirmation_menu = new Menu( + context, + Bukkit.createInventory(null, columns, LegacyComponentSerializer.legacySection().deserialize(title)) + ); + final var confirm_index = (int) (Math.random() * columns); - for (int i = 0; i < columns; ++i) { - if (i == confirm_index) { - confirmation_menu.add( - new MenuItem( - i, - item_confirm, - (player, menu, self) -> { - menu.close(player); - return on_confirm.apply(player); - } - ) - ); - } else { - confirmation_menu.add( - new MenuItem( - i, - item_cancel, - (player, menu, self) -> { - menu.close(player); - on_cancel.apply(player); - return ClickResult.SUCCESS; - } - ) - ); - } - } + for (int i = 0; i < columns; ++i) { + if (i == confirm_index) { + confirmation_menu.add( + new MenuItem(i, item_confirm, (player, menu, self) -> { + menu.close(player); + return on_confirm.apply(player); + }) + ); + } else { + confirmation_menu.add( + new MenuItem(i, item_cancel, (player, menu, self) -> { + menu.close(player); + on_cancel.apply(player); + return ClickResult.SUCCESS; + }) + ); + } + } - // On natural close call cancel - confirmation_menu.on_natural_close(on_cancel); + // On natural close call cancel + confirmation_menu.on_natural_close(on_cancel); - return confirmation_menu; - } + return confirmation_menu; + } - public static Menu item_selector( - final Context context, - final Player player, - final String title, - @Nullable final ItemStack initial_item, - boolean allow_nothing, - final Function2 on_confirm, - final Consumer1 on_cancel - ) { - return item_selector(context, player, title, initial_item, allow_nothing, on_confirm, on_cancel, i -> i); - } + public static Menu item_selector( + final Context context, + final Player player, + final String title, + @Nullable final ItemStack initial_item, + boolean allow_nothing, + final Function2 on_confirm, + final Consumer1 on_cancel + ) { + return item_selector(context, player, title, initial_item, allow_nothing, on_confirm, on_cancel, i -> i); + } - public static Menu item_selector( - final Context context, - final Player player, - final String title, - @Nullable final ItemStack initial_item, - boolean allow_nothing, - final Function2 on_confirm, - final Consumer1 on_cancel, - final Function1 on_select_item - ) { - final var menu_manager = context.get_module().core.menu_manager; - final Function1 set_item_name = item -> name_item( - item, - menu_manager.item_selector_selected.lang_name.format(), - menu_manager.item_selector_selected.lang_lore.format() - ); + public static Menu item_selector( + final Context context, + final Player player, + final String title, + @Nullable final ItemStack initial_item, + boolean allow_nothing, + final Function2 on_confirm, + final Consumer1 on_cancel, + final Function1 on_select_item + ) { + final var menu_manager = context.get_module().core.menu_manager; + final Function1 set_item_name = item -> + name_item( + item, + menu_manager.item_selector_selected.lang_name.format(), + menu_manager.item_selector_selected.lang_lore.format() + ); - final var no_item = set_item_name.apply(new ItemStack(Material.BARRIER)); - final ItemStack default_item; - if (initial_item == null || initial_item.getType() == Material.AIR) { - default_item = no_item; - } else { - default_item = initial_item; - } + final var no_item = set_item_name.apply(new ItemStack(Material.BARRIER)); + final ItemStack default_item; + if (initial_item == null || initial_item.getType() == Material.AIR) { + default_item = no_item; + } else { + default_item = initial_item; + } - final var columns = 9; - final var item_selector_menu = new Menu( - context, - Bukkit.createInventory(null, columns, LegacyComponentSerializer.legacySection().deserialize(title)) - ); - final var selected_item = new MenuItem( - 4, - default_item, - (p, menu, self, event) -> { - if (!Menu.is_left_or_right_click(event)) { - return ClickResult.INVALID_CLICK; - } + final var columns = 9; + final var item_selector_menu = new Menu( + context, + Bukkit.createInventory(null, columns, LegacyComponentSerializer.legacySection().deserialize(title)) + ); + final var selected_item = new MenuItem(4, default_item, (p, menu, self, event) -> { + if (!Menu.is_left_or_right_click(event)) { + return ClickResult.INVALID_CLICK; + } - if (allow_nothing && event.getClick() == ClickType.RIGHT) { - // Clear selection - self.update_item(menu, no_item); - } else { - // Reset selection - self.update_item(menu, default_item); - } - return ClickResult.SUCCESS; - } - ) { - public ItemStack original_selected = null; + if (allow_nothing && event.getClick() == ClickType.RIGHT) { + // Clear selection + self.update_item(menu, no_item); + } else { + // Reset selection + self.update_item(menu, default_item); + } + return ClickResult.SUCCESS; + }) { + public ItemStack original_selected = null; - @Override - public void item(final ItemStack item) { - this.original_selected = item; - super.item(set_item_name.apply(item.clone())); - } - }; + @Override + public void item(final ItemStack item) { + this.original_selected = item; + super.item(set_item_name.apply(item.clone())); + } + }; - // Selected item, begin with default selected - selected_item.item(default_item); - item_selector_menu.add(selected_item); + // Selected item, begin with default selected + selected_item.item(default_item); + item_selector_menu.add(selected_item); - // Inventory listener - item_selector_menu.add( - new MenuItemClickListener( - -1, - (p, menu, item) -> { - // Called when any item in inventory is clicked - if (item == null) { - return ClickResult.IGNORE; - } + // Inventory listener + item_selector_menu.add( + new MenuItemClickListener(-1, (p, menu, item) -> { + // Called when any item in inventory is clicked + if (item == null) { + return ClickResult.IGNORE; + } - // Call on_select and check if the resulting item is valid - item = on_select_item.apply(item.clone()); - if (item == null) { - return ClickResult.ERROR; - } + // Call on_select and check if the resulting item is valid + item = on_select_item.apply(item.clone()); + if (item == null) { + return ClickResult.ERROR; + } - selected_item.item(item); - menu.update(); - return ClickResult.SUCCESS; - } - ) - ); + selected_item.item(item); + menu.update(); + return ClickResult.SUCCESS; + }) + ); - // Accept item - item_selector_menu.add( - new MenuItem( - 2, - menu_manager.item_selector_accept.item(), - (p, menu, self) -> { - final ItemStack item; - if (selected_item.original_selected == no_item) { - if (allow_nothing) { - item = null; - } else { - return ClickResult.ERROR; - } - } else { - item = selected_item.original_selected; - } + // Accept item + item_selector_menu.add( + new MenuItem(2, menu_manager.item_selector_accept.item(), (p, menu, self) -> { + final ItemStack item; + if (selected_item.original_selected == no_item) { + if (allow_nothing) { + item = null; + } else { + return ClickResult.ERROR; + } + } else { + item = selected_item.original_selected; + } - menu.close(p); - return on_confirm.apply(p, item); - } - ) - ); + menu.close(p); + return on_confirm.apply(p, item); + }) + ); - // Cancel item - item_selector_menu.add( - new MenuItem( - 6, - menu_manager.item_selector_cancel.item(), - (p, menu, self) -> { - menu.close(p); - on_cancel.apply(player); - return ClickResult.SUCCESS; - } - ) - ); + // Cancel item + item_selector_menu.add( + new MenuItem(6, menu_manager.item_selector_cancel.item(), (p, menu, self) -> { + menu.close(p); + on_cancel.apply(player); + return ClickResult.SUCCESS; + }) + ); - // On natural close call cancel - item_selector_menu.on_natural_close(on_cancel); + // On natural close call cancel + item_selector_menu.on_natural_close(on_cancel); - return item_selector_menu; - } + return item_selector_menu; + } - public static > Menu generic_selector( - final Context context, - final Player player, - final String title, - final String filter_title, - final List things, - final Function1 to_item, - final F filter, - final Function3 on_click, - final Consumer1 on_cancel - ) { - return generic_selector( - context, - player, - title, - filter_title, - things, - to_item, - filter, - (p, menu, t, event) -> { - if (!Menu.is_left_click(event)) { - return ClickResult.INVALID_CLICK; - } - return on_click.apply(p, menu, t); - }, - on_cancel - ); - } + public static > Menu generic_selector( + final Context context, + final Player player, + final String title, + final String filter_title, + final List things, + final Function1 to_item, + final F filter, + final Function3 on_click, + final Consumer1 on_cancel + ) { + return generic_selector( + context, + player, + title, + filter_title, + things, + to_item, + filter, + (p, menu, t, event) -> { + if (!Menu.is_left_click(event)) { + return ClickResult.INVALID_CLICK; + } + return on_click.apply(p, menu, t); + }, + on_cancel + ); + } - public static > Menu generic_selector( - final Context context, - final Player player, - final String title, - final String filter_title, - final List things, - final Function1 to_item, - final F filter, - final Function4 on_click, - final Consumer1 on_cancel - ) { - return GenericSelector.create( - context, - player, - title, - filter_title, - things, - to_item, - filter, - on_click, - on_cancel - ); - } + public static > Menu generic_selector( + final Context context, + final Player player, + final String title, + final String filter_title, + final List things, + final Function1 to_item, + final F filter, + final Function4 on_click, + final Consumer1 on_cancel + ) { + return GenericSelector.create( + context, + player, + title, + filter_title, + things, + to_item, + filter, + on_click, + on_cancel + ); + } - public static Menu head_selector( - final Context context, - final Player player, - final Function3 on_click, - final Consumer1 on_cancel - ) { - return head_selector( - context, - player, - (p, menu, t, event) -> { - if (!Menu.is_left_click(event)) { - return ClickResult.INVALID_CLICK; - } - return on_click.apply(p, menu, t); - }, - on_cancel - ); - } + public static Menu head_selector( + final Context context, + final Player player, + final Function3 on_click, + final Consumer1 on_cancel + ) { + return head_selector( + context, + player, + (p, menu, t, event) -> { + if (!Menu.is_left_click(event)) { + return ClickResult.INVALID_CLICK; + } + return on_click.apply(p, menu, t); + }, + on_cancel + ); + } - public static Menu head_selector( - final Context context, - final Player player, - final Function4 on_click, - final Consumer1 on_cancel - ) { - final var menu_manager = context.get_module().core.menu_manager; - final var all_heads = HeadMaterialLibrary - .all() - .stream() - .sorted((a, b) -> a.key().toString().compareToIgnoreCase(b.key().toString())) - .collect(Collectors.toList()); + public static Menu head_selector( + final Context context, + final Player player, + final Function4 on_click, + final Consumer1 on_cancel + ) { + final var menu_manager = context.get_module().core.menu_manager; + final var all_heads = HeadMaterialLibrary.all() + .stream() + .sorted((a, b) -> a.key().toString().compareToIgnoreCase(b.key().toString())) + .collect(Collectors.toList()); - final var filter = new HeadFilter(); - return MenuFactory.generic_selector( - context, - player, - menu_manager.head_selector.lang_title.str("§5§l" + all_heads.size()), - menu_manager.head_selector.lang_filter_title.str(), - all_heads, - h -> - menu_manager.head_selector.item_select_head.alternative( - h.item(), - "§a§l" + h.name(), - "§6" + h.category(), - "§b" + h.tags() - ), - filter, - on_click, - on_cancel - ); - } + final var filter = new HeadFilter(); + return MenuFactory.generic_selector( + context, + player, + menu_manager.head_selector.lang_title.str("§5§l" + all_heads.size()), + menu_manager.head_selector.lang_filter_title.str(), + all_heads, + h -> + menu_manager.head_selector.item_select_head.alternative( + h.item(), + "§a§l" + h.name(), + "§6" + h.category(), + "§b" + h.tags() + ), + filter, + on_click, + on_cancel + ); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItem.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItem.java index df06878e3..a0206b0aa 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItem.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItem.java @@ -9,87 +9,83 @@ public class MenuItem implements MenuWidget { - private int slot; - private Function4 on_click; - private ItemStack item; - private boolean auto_update; + private int slot; + private Function4 on_click; + private ItemStack item; + private boolean auto_update; - public MenuItem(int slot, final ItemStack item) { - this(slot, item, (Function4) null); - } + public MenuItem(int slot, final ItemStack item) { + this(slot, item, (Function4) null); + } - public MenuItem(int slot, final ItemStack item, final Function3 on_click) { - this( - slot, - item, - (player, menu, self, event) -> { - if (!Menu.is_left_click(event)) { - return ClickResult.INVALID_CLICK; - } - return on_click.apply(player, menu, self); - } - ); - } + public MenuItem(int slot, final ItemStack item, final Function3 on_click) { + this(slot, item, (player, menu, self, event) -> { + if (!Menu.is_left_click(event)) { + return ClickResult.INVALID_CLICK; + } + return on_click.apply(player, menu, self); + }); + } - public MenuItem( - int slot, - final ItemStack item, - final Function4 on_click - ) { - this.slot = slot; - this.on_click = on_click; - auto_update = item == null; - item(item); - } + public MenuItem( + int slot, + final ItemStack item, + final Function4 on_click + ) { + this.slot = slot; + this.on_click = on_click; + auto_update = item == null; + item(item); + } - public int slot() { - return slot; - } + public int slot() { + return slot; + } - public ItemStack item(final Menu menu) { - return menu.inventory().getItem(slot); - } + public ItemStack item(final Menu menu) { + return menu.inventory().getItem(slot); + } - public void item(final ItemStack item) { - this.item = item; - } + public void item(final ItemStack item) { + this.item = item; + } - public void update_item(final Menu menu, final ItemStack item) { - this.item(item); - menu.update(); - } + public void update_item(final Menu menu, final ItemStack item) { + this.item(item); + menu.update(); + } - @Override - public boolean update(final Menu menu) { - if (auto_update) { - this.item((ItemStack) null); - } + @Override + public boolean update(final Menu menu) { + if (auto_update) { + this.item((ItemStack) null); + } - final var cur = item(menu); - if (cur != item) { - menu.inventory().setItem(slot(), item); - return true; - } else { - return false; - } - } + final var cur = item(menu); + if (cur != item) { + menu.inventory().setItem(slot(), item); + return true; + } else { + return false; + } + } - @Override - public ClickResult click( - final Player player, - final Menu menu, - final ItemStack item, - int slot, - final InventoryClickEvent event - ) { - if (this.slot != slot) { - return ClickResult.IGNORE; - } + @Override + public ClickResult click( + final Player player, + final Menu menu, + final ItemStack item, + int slot, + final InventoryClickEvent event + ) { + if (this.slot != slot) { + return ClickResult.IGNORE; + } - if (on_click != null) { - return on_click.apply(player, menu, this, event); - } else { - return ClickResult.IGNORE; - } - } + if (on_click != null) { + return on_click.apply(player, menu, this, event); + } else { + return ClickResult.IGNORE; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItemClickListener.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItemClickListener.java index 6d30e3b1f..803f6f6bc 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItemClickListener.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuItemClickListener.java @@ -9,52 +9,49 @@ public class MenuItemClickListener implements MenuWidget { - private int slot; - private Function4 on_click; - - public MenuItemClickListener(int slot, final Function3 on_click) { - this( - slot, - (player, menu, item, event) -> { - if (!Menu.is_left_click(event)) { - return ClickResult.INVALID_CLICK; - } - return on_click.apply(player, menu, item); - } - ); - } - - public MenuItemClickListener( - int slot, - final Function4 on_click - ) { - this.slot = slot; - this.on_click = on_click; - } - - public int slot() { - return slot; - } - - public boolean update(final Menu menu) { - return false; - } - - public ClickResult click( - final Player player, - final Menu menu, - final ItemStack item, - int slot, - final InventoryClickEvent event - ) { - if (this.slot != slot) { - return ClickResult.IGNORE; - } - - if (on_click != null) { - return on_click.apply(player, menu, item, event); - } else { - return ClickResult.IGNORE; - } - } + private int slot; + private Function4 on_click; + + public MenuItemClickListener(int slot, final Function3 on_click) { + this(slot, (player, menu, item, event) -> { + if (!Menu.is_left_click(event)) { + return ClickResult.INVALID_CLICK; + } + return on_click.apply(player, menu, item); + }); + } + + public MenuItemClickListener( + int slot, + final Function4 on_click + ) { + this.slot = slot; + this.on_click = on_click; + } + + public int slot() { + return slot; + } + + public boolean update(final Menu menu) { + return false; + } + + public ClickResult click( + final Player player, + final Menu menu, + final ItemStack item, + int slot, + final InventoryClickEvent event + ) { + if (this.slot != slot) { + return ClickResult.IGNORE; + } + + if (on_click != null) { + return on_click.apply(player, menu, item, event); + } else { + return ClickResult.IGNORE; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuManager.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuManager.java index 4affee61d..cc1bb5c93 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuManager.java @@ -20,171 +20,189 @@ public class MenuManager extends Listener { - private final HashMap open_menus = new HashMap<>(); - private final HashMap menus = new HashMap<>(); - - public TranslatedItemStack item_selector_accept; - public TranslatedItemStack item_selector_cancel; - public TranslatedItemStack item_selector_selected; - - public TranslatedItemStack generic_selector_page; - public TranslatedItemStack generic_selector_current_page; - public TranslatedItemStack generic_selector_filter; - public TranslatedItemStack generic_selector_cancel; - - public HeadSelectorGroup head_selector; - - public MenuManager(Context context) { - super(context.namespace("menus")); - final var ctx = get_context(); - head_selector = new HeadSelectorGroup(ctx); - - final var ctx_item_selector = ctx.namespace("item_selector", "Menu configuration for item selector menus."); - item_selector_accept = new TranslatedItemStack<>( - ctx_item_selector, - "accept", - Material.LIME_TERRACOTTA, - 1, - "Used to confirm item selection."); - item_selector_cancel = new TranslatedItemStack<>( - ctx_item_selector, - "cancel", - Material.RED_TERRACOTTA, - 1, - "Used to cancel item selection."); - item_selector_selected = new TranslatedItemStack<>( - ctx_item_selector, - "selected", - Material.BARRIER, - 1, - "Represents the selected item. Left-clicking will reset the selection to the initial value, and right-clicking will clear the selected item. The given stack is used as the 'empty', cleared item."); - - final var ctx_generic_selector = ctx.namespace( - "generic_selector", - "Menu configuration for generic selector menus."); - generic_selector_page = new TranslatedItemStack<>(ctx_generic_selector, "page", Material.PAPER, 1, - "Used to select pages."); - generic_selector_current_page = new TranslatedItemStack<>( - ctx_generic_selector, - "current_page", - Material.MAP, - 1, - "Used to indicate current page."); - generic_selector_filter = new TranslatedItemStack<>(ctx_generic_selector, "filter", Material.HOPPER, 1, - "Used to filter items."); - generic_selector_cancel = new TranslatedItemStack<>( - ctx_generic_selector, - "cancel", - Material.PRISMARINE_SHARD, - 1, - "Used to cancel selection."); - } - - public Menu menu_for(final Player player, final InventoryView view) { - return menu_for(player, view.getTopInventory()); - } - - public Menu menu_for(final Player player, final Inventory inventory) { - final var menu = menus.get(inventory); - final var open = open_menus.get(player.getUniqueId()); - if (open != menu && menu != null) { - get_module().log.warning( - "Menu inconsistency: entity " + - player + - " accessed a menu '" + - open_menus.get(player.getUniqueId()) + - "' that isn't registered to it. The registered menu is '" + - menu + - "'"); - return menu; - } - return menu == null ? open : menu; - } - - public void add(final Player player, final Menu menu) { - open_menus.put(player.getUniqueId(), menu); - menus.put(menu.inventory(), menu); - } - - public void remove(final Player player, final Menu menu) { - open_menus.remove(player.getUniqueId()); - final var orphaned = open_menus.values().stream().allMatch(m -> m != menu); - - // Remove orphaned menus from other maps - if (orphaned) { - menus.remove(menu.inventory()); - } - } - - public void for_each_open(final Consumer2 functor) { - for (final var player : get_module().getServer().getOnlinePlayers()) { - final var open = open_menus.get(player.getUniqueId()); - if (open == null) { - continue; - } - - functor.apply(player, open); - } - } - - public void update(final Menu menu) { - get_module() - .getServer() - .getOnlinePlayers() - .stream() - .filter(p -> open_menus.get(p.getUniqueId()) == menu) - .forEach(p -> p.updateInventory()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_inventory_click(final InventoryClickEvent event) { - final var clicker = event.getWhoClicked(); - if (!(clicker instanceof Player)) { - return; - } - - final var player = (Player) clicker; - final var menu = menu_for(player, event.getView()); - if (menu != null) { - event.setCancelled(true); - final var slot = event.getClickedInventory() == menu.inventory() ? event.getSlot() : -1; - menu.click(player, event.getCurrentItem(), slot, event); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_inventory_drag(final InventoryDragEvent event) { - final var clicker = event.getWhoClicked(); - if (!(clicker instanceof Player)) { - return; - } - - final var player = (Player) clicker; - final var menu = menu_for(player, event.getView()); - if (menu != null) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_inventory_close(final InventoryCloseEvent event) { - final var human = event.getPlayer(); - if (!(human instanceof Player)) { - return; - } - - final var player = (Player) human; - final var menu = menu_for(player, event.getView()); - if (menu != null) { - menu.closed(player, event.getReason()); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_prepare_anvil_event(final PrepareAnvilEvent event) { - final var menu = menus.get(event.getView().getTopInventory()); - if (menu != null) { - event.getView().setRepairCost(0); - } - } + private final HashMap open_menus = new HashMap<>(); + private final HashMap menus = new HashMap<>(); + + public TranslatedItemStack item_selector_accept; + public TranslatedItemStack item_selector_cancel; + public TranslatedItemStack item_selector_selected; + + public TranslatedItemStack generic_selector_page; + public TranslatedItemStack generic_selector_current_page; + public TranslatedItemStack generic_selector_filter; + public TranslatedItemStack generic_selector_cancel; + + public HeadSelectorGroup head_selector; + + public MenuManager(Context context) { + super(context.namespace("menus")); + final var ctx = get_context(); + head_selector = new HeadSelectorGroup(ctx); + + final var ctx_item_selector = ctx.namespace("item_selector", "Menu configuration for item selector menus."); + item_selector_accept = new TranslatedItemStack<>( + ctx_item_selector, + "accept", + Material.LIME_TERRACOTTA, + 1, + "Used to confirm item selection." + ); + item_selector_cancel = new TranslatedItemStack<>( + ctx_item_selector, + "cancel", + Material.RED_TERRACOTTA, + 1, + "Used to cancel item selection." + ); + item_selector_selected = new TranslatedItemStack<>( + ctx_item_selector, + "selected", + Material.BARRIER, + 1, + "Represents the selected item. Left-clicking will reset the selection to the initial value, and right-clicking will clear the selected item. The given stack is used as the 'empty', cleared item." + ); + + final var ctx_generic_selector = ctx.namespace( + "generic_selector", + "Menu configuration for generic selector menus." + ); + generic_selector_page = new TranslatedItemStack<>( + ctx_generic_selector, + "page", + Material.PAPER, + 1, + "Used to select pages." + ); + generic_selector_current_page = new TranslatedItemStack<>( + ctx_generic_selector, + "current_page", + Material.MAP, + 1, + "Used to indicate current page." + ); + generic_selector_filter = new TranslatedItemStack<>( + ctx_generic_selector, + "filter", + Material.HOPPER, + 1, + "Used to filter items." + ); + generic_selector_cancel = new TranslatedItemStack<>( + ctx_generic_selector, + "cancel", + Material.PRISMARINE_SHARD, + 1, + "Used to cancel selection." + ); + } + + public Menu menu_for(final Player player, final InventoryView view) { + return menu_for(player, view.getTopInventory()); + } + + public Menu menu_for(final Player player, final Inventory inventory) { + final var menu = menus.get(inventory); + final var open = open_menus.get(player.getUniqueId()); + if (open != menu && menu != null) { + get_module() + .log.warning( + "Menu inconsistency: entity " + + player + + " accessed a menu '" + + open_menus.get(player.getUniqueId()) + + "' that isn't registered to it. The registered menu is '" + + menu + + "'" + ); + return menu; + } + return menu == null ? open : menu; + } + + public void add(final Player player, final Menu menu) { + open_menus.put(player.getUniqueId(), menu); + menus.put(menu.inventory(), menu); + } + + public void remove(final Player player, final Menu menu) { + open_menus.remove(player.getUniqueId()); + final var orphaned = open_menus.values().stream().allMatch(m -> m != menu); + + // Remove orphaned menus from other maps + if (orphaned) { + menus.remove(menu.inventory()); + } + } + + public void for_each_open(final Consumer2 functor) { + for (final var player : get_module().getServer().getOnlinePlayers()) { + final var open = open_menus.get(player.getUniqueId()); + if (open == null) { + continue; + } + + functor.apply(player, open); + } + } + + public void update(final Menu menu) { + get_module() + .getServer() + .getOnlinePlayers() + .stream() + .filter(p -> open_menus.get(p.getUniqueId()) == menu) + .forEach(p -> p.updateInventory()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_inventory_click(final InventoryClickEvent event) { + final var clicker = event.getWhoClicked(); + if (!(clicker instanceof Player)) { + return; + } + + final var player = (Player) clicker; + final var menu = menu_for(player, event.getView()); + if (menu != null) { + event.setCancelled(true); + final var slot = event.getClickedInventory() == menu.inventory() ? event.getSlot() : -1; + menu.click(player, event.getCurrentItem(), slot, event); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_inventory_drag(final InventoryDragEvent event) { + final var clicker = event.getWhoClicked(); + if (!(clicker instanceof Player)) { + return; + } + + final var player = (Player) clicker; + final var menu = menu_for(player, event.getView()); + if (menu != null) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_inventory_close(final InventoryCloseEvent event) { + final var human = event.getPlayer(); + if (!(human instanceof Player)) { + return; + } + + final var player = (Player) human; + final var menu = menu_for(player, event.getView()); + if (menu != null) { + menu.closed(player, event.getReason()); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_prepare_anvil_event(final PrepareAnvilEvent event) { + final var menu = menus.get(event.getView().getTopInventory()); + if (menu != null) { + event.getView().setRepairCost(0); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuWidget.java b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuWidget.java index 61032bb5c..f5dc33056 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuWidget.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/menu/MenuWidget.java @@ -6,13 +6,13 @@ import org.oddlama.vane.core.menu.Menu.ClickResult; public interface MenuWidget { - public boolean update(final Menu menu); + public boolean update(final Menu menu); - public ClickResult click( - final Player player, - final Menu menu, - final ItemStack item, - int slot, - final InventoryClickEvent event - ); + public ClickResult click( + final Player player, + final Menu menu, + final ItemStack item, + int slot, + final InventoryClickEvent event + ); } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/misc/AuthMultiplexer.java b/vane-core/src/main/java/org/oddlama/vane/core/misc/AuthMultiplexer.java index 9882faa06..6e842a457 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/misc/AuthMultiplexer.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/misc/AuthMultiplexer.java @@ -2,6 +2,7 @@ import static org.oddlama.vane.util.Resolve.resolve_skin; +import com.destroystokyo.paper.profile.ProfileProperty; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; @@ -10,9 +11,7 @@ import java.util.Map; import java.util.UUID; import java.util.logging.Level; - -import com.destroystokyo.paper.profile.ProfileProperty; - +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -23,125 +22,132 @@ import org.oddlama.vane.core.Core; import org.oddlama.vane.core.Listener; import org.oddlama.vane.core.module.Context; - -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.oddlama.vane.util.Resolve; public class AuthMultiplexer extends Listener implements PluginMessageListener { - // Channel for proxy messages to multiplex connections - public static final String CHANNEL_AUTH_MULTIPLEX = "vane_proxy:auth_multiplex"; - - // Persistent storage - @Persistent - public Map storage_auth_multiplex = new HashMap<>(); - - @Persistent - public Map storage_auth_multiplexer_id = new HashMap<>(); - - public AuthMultiplexer(Context context) { - super(context); - } - - @Override - protected void on_enable() { - super.on_enable(); - get_module().getServer().getMessenger().registerIncomingPluginChannel(get_module(), CHANNEL_AUTH_MULTIPLEX, this); - } - - @Override - protected void on_disable() { - super.on_disable(); - get_module().getServer().getMessenger().unregisterIncomingPluginChannel(get_module(), CHANNEL_AUTH_MULTIPLEX, this); - } - - public synchronized String auth_multiplex_player_name(final UUID uuid) { - final var original_player_id = storage_auth_multiplex.get(uuid); - final var multiplexer_id = storage_auth_multiplexer_id.get(uuid); - if (original_player_id == null || multiplexer_id == null) { - return null; - } - - final var original_player = get_module().getServer().getOfflinePlayer(original_player_id); - return "§7[" + multiplexer_id + "]§r " + original_player.getName(); - } - - private void try_init_multiplexed_player_name(final Player player) { - final var id = player.getUniqueId(); - final var display_name = auth_multiplex_player_name(id); - if (display_name == null) { - return; - } - - get_module().log.info( - "[multiplex] Init player '" + - display_name + - "' for registered auth multiplexed player {" + - id + - ", " + - player.getName() + - "}" - ); - final var display_name_component = LegacyComponentSerializer.legacySection().deserialize(display_name); - player.displayName(display_name_component); - player.playerListName(display_name_component); - - final var original_player_id = storage_auth_multiplex.get(id); - Resolve.Skin skin; - try { - skin = resolve_skin(original_player_id); - } catch (IOException | URISyntaxException e) { - Bukkit.getLogger().log(Level.WARNING, "Failed to resolve skin for uuid '" + id + "'", e); - return; - } - - final var profile = player.getPlayerProfile(); - profile.setProperty(new ProfileProperty("textures", skin.texture, skin.signature)); - player.setPlayerProfile(profile); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) - public void on_player_join(PlayerJoinEvent event) { - try_init_multiplexed_player_name(event.getPlayer()); - } - - @Override - public synchronized void onPluginMessageReceived(final String channel, final Player player, byte[] bytes) { - if (!channel.equals(CHANNEL_AUTH_MULTIPLEX)) { - return; - } - - final var stream = new ByteArrayInputStream(bytes); - final var in = new DataInputStream(stream); - - try { - final var multiplexer_id = in.readInt(); - final var old_uuid = UUID.fromString(in.readUTF()); - final var old_name = in.readUTF(); - final var new_uuid = UUID.fromString(in.readUTF()); - final var new_name = in.readUTF(); - - get_module().log.info( - "[multiplex] Registered auth multiplexed player {" + - new_uuid + - ", " + - new_name + - "} from player {" + - old_uuid + - ", " + - old_name + - "} multiplexer_id " + - multiplexer_id - ); - storage_auth_multiplex.put(new_uuid, old_uuid); - storage_auth_multiplexer_id.put(new_uuid, multiplexer_id); - mark_persistent_storage_dirty(); - - final var multiplexed_player = get_module().getServer().getOfflinePlayer(new_uuid); - if (multiplexed_player.isOnline()) { - try_init_multiplexed_player_name(multiplexed_player.getPlayer()); - } - } catch (IOException e) { - e.printStackTrace(); - } - } + + // Channel for proxy messages to multiplex connections + public static final String CHANNEL_AUTH_MULTIPLEX = "vane_proxy:auth_multiplex"; + + // Persistent storage + @Persistent + public Map storage_auth_multiplex = new HashMap<>(); + + @Persistent + public Map storage_auth_multiplexer_id = new HashMap<>(); + + public AuthMultiplexer(Context context) { + super(context); + } + + @Override + protected void on_enable() { + super.on_enable(); + get_module() + .getServer() + .getMessenger() + .registerIncomingPluginChannel(get_module(), CHANNEL_AUTH_MULTIPLEX, this); + } + + @Override + protected void on_disable() { + super.on_disable(); + get_module() + .getServer() + .getMessenger() + .unregisterIncomingPluginChannel(get_module(), CHANNEL_AUTH_MULTIPLEX, this); + } + + public synchronized String auth_multiplex_player_name(final UUID uuid) { + final var original_player_id = storage_auth_multiplex.get(uuid); + final var multiplexer_id = storage_auth_multiplexer_id.get(uuid); + if (original_player_id == null || multiplexer_id == null) { + return null; + } + + final var original_player = get_module().getServer().getOfflinePlayer(original_player_id); + return "§7[" + multiplexer_id + "]§r " + original_player.getName(); + } + + private void try_init_multiplexed_player_name(final Player player) { + final var id = player.getUniqueId(); + final var display_name = auth_multiplex_player_name(id); + if (display_name == null) { + return; + } + + get_module() + .log.info( + "[multiplex] Init player '" + + display_name + + "' for registered auth multiplexed player {" + + id + + ", " + + player.getName() + + "}" + ); + final var display_name_component = LegacyComponentSerializer.legacySection().deserialize(display_name); + player.displayName(display_name_component); + player.playerListName(display_name_component); + + final var original_player_id = storage_auth_multiplex.get(id); + Resolve.Skin skin; + try { + skin = resolve_skin(original_player_id); + } catch (IOException | URISyntaxException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to resolve skin for uuid '" + id + "'", e); + return; + } + + final var profile = player.getPlayerProfile(); + profile.setProperty(new ProfileProperty("textures", skin.texture, skin.signature)); + player.setPlayerProfile(profile); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) + public void on_player_join(PlayerJoinEvent event) { + try_init_multiplexed_player_name(event.getPlayer()); + } + + @Override + public synchronized void onPluginMessageReceived(final String channel, final Player player, byte[] bytes) { + if (!channel.equals(CHANNEL_AUTH_MULTIPLEX)) { + return; + } + + final var stream = new ByteArrayInputStream(bytes); + final var in = new DataInputStream(stream); + + try { + final var multiplexer_id = in.readInt(); + final var old_uuid = UUID.fromString(in.readUTF()); + final var old_name = in.readUTF(); + final var new_uuid = UUID.fromString(in.readUTF()); + final var new_name = in.readUTF(); + + get_module() + .log.info( + "[multiplex] Registered auth multiplexed player {" + + new_uuid + + ", " + + new_name + + "} from player {" + + old_uuid + + ", " + + old_name + + "} multiplexer_id " + + multiplexer_id + ); + storage_auth_multiplex.put(new_uuid, old_uuid); + storage_auth_multiplexer_id.put(new_uuid, multiplexer_id); + mark_persistent_storage_dirty(); + + final var multiplexed_player = get_module().getServer().getOfflinePlayer(new_uuid); + if (multiplexed_player.isOnline()) { + try_init_multiplexed_player_name(multiplexed_player.getPlayer()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/misc/CommandHider.java b/vane-core/src/main/java/org/oddlama/vane/core/misc/CommandHider.java index 719c973a4..ff3b4c3d0 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/misc/CommandHider.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/misc/CommandHider.java @@ -10,42 +10,42 @@ public class CommandHider extends Listener { - public CommandHider(Context context) { - super( - context.group( - "hide_commands", - "Hide error messages for all commands for which a player has no permission, by displaying the default unknown command message instead." - ) - ); - } - - private boolean allow_command_event(String message, Player player) { - message = message.trim(); - if (!message.startsWith("/")) { - return false; - } - - var id = message.substring(1); - final var space_index = id.indexOf(' '); - if (space_index > -1) { - id = id.substring(0, space_index); - } - - final var command_map = get_module().getServer().getCommandMap().getKnownCommands(); - var command = command_map.get(id); - if (command != null) { - return command.testPermissionSilent(player); - } - - return true; - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_command_preprocess(PlayerCommandPreprocessEvent event) { - if (!allow_command_event(event.getMessage(), event.getPlayer())) { - final var msg = get_module().getServer().spigot().getSpigotConfig().getString("messages.unknown-command"); - event.getPlayer().sendMessage(msg); - event.setCancelled(true); - } - } + public CommandHider(Context context) { + super( + context.group( + "hide_commands", + "Hide error messages for all commands for which a player has no permission, by displaying the default unknown command message instead." + ) + ); + } + + private boolean allow_command_event(String message, Player player) { + message = message.trim(); + if (!message.startsWith("/")) { + return false; + } + + var id = message.substring(1); + final var space_index = id.indexOf(' '); + if (space_index > -1) { + id = id.substring(0, space_index); + } + + final var command_map = get_module().getServer().getCommandMap().getKnownCommands(); + var command = command_map.get(id); + if (command != null) { + return command.testPermissionSilent(player); + } + + return true; + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_command_preprocess(PlayerCommandPreprocessEvent event) { + if (!allow_command_event(event.getMessage(), event.getPlayer())) { + final var msg = get_module().getServer().spigot().getSpigotConfig().getString("messages.unknown-command"); + event.getPlayer().sendMessage(msg); + event.setCancelled(true); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/misc/HeadLibrary.java b/vane-core/src/main/java/org/oddlama/vane/core/misc/HeadLibrary.java index a00f9f375..d1c98d4fe 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/misc/HeadLibrary.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/misc/HeadLibrary.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.logging.Level; - import org.apache.commons.io.IOUtils; import org.bukkit.Material; import org.bukkit.block.Skull; @@ -20,47 +19,52 @@ import org.oddlama.vane.core.module.Context; public class HeadLibrary extends Listener { - @ConfigBoolean(def = true, desc = "When a player head is broken by a player that exists in /heads, drop the correctly named item as seen in /heads. You can disable this if it interferes with similarly textured heads from other plugins.") - public boolean config_player_head_drops; - public HeadLibrary(Context context) { - super(context); + @ConfigBoolean( + def = true, + desc = "When a player head is broken by a player that exists in /heads, drop the correctly named item as seen in /heads. You can disable this if it interferes with similarly textured heads from other plugins." + ) + public boolean config_player_head_drops; - // Load a head material library - get_module().log.info("Loading head library..."); - try { - HeadMaterialLibrary.load(IOUtils.toString(get_module().getResource("head_library.json"), StandardCharsets.UTF_8)); - } catch (IOException e) { - get_module().log.log(Level.SEVERE, "Error while loading head_library.json! Shutting down.", e); - get_module().getServer().shutdown(); - } - } + public HeadLibrary(Context context) { + super(context); + // Load a head material library + get_module().log.info("Loading head library..."); + try { + HeadMaterialLibrary.load( + IOUtils.toString(get_module().getResource("head_library.json"), StandardCharsets.UTF_8) + ); + } catch (IOException e) { + get_module().log.log(Level.SEVERE, "Error while loading head_library.json! Shutting down.", e); + get_module().getServer().shutdown(); + } + } - // Restore correct head item from a head library when broken - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_block_break(final BlockBreakEvent event) { - if (!config_player_head_drops) { - return; - } + // Restore correct head item from a head library when broken + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_block_break(final BlockBreakEvent event) { + if (!config_player_head_drops) { + return; + } - final var block = event.getBlock(); - if (block.getType() != Material.PLAYER_HEAD && block.getType() != Material.PLAYER_WALL_HEAD) { - return; - } + final var block = event.getBlock(); + if (block.getType() != Material.PLAYER_HEAD && block.getType() != Material.PLAYER_WALL_HEAD) { + return; + } - final var skull = (Skull) block.getState(); - final var texture = texture_from_skull(skull); - if (texture == null) { - return; - } + final var skull = (Skull) block.getState(); + final var texture = texture_from_skull(skull); + if (texture == null) { + return; + } - final var head_material = HeadMaterialLibrary.from_texture(texture); - if (head_material == null) { - return; - } + final var head_material = HeadMaterialLibrary.from_texture(texture); + if (head_material == null) { + return; + } - // Set to air and drop item - block.setType(Material.AIR); - drop_naturally(block, head_material.item()); - } + // Set to air and drop item + block.setType(Material.AIR); + drop_naturally(block, head_material.item()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/misc/LootChestProtector.java b/vane-core/src/main/java/org/oddlama/vane/core/misc/LootChestProtector.java index cb1ed4de6..3facd4c98 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/misc/LootChestProtector.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/misc/LootChestProtector.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; - import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -17,62 +16,63 @@ import org.oddlama.vane.core.module.Context; public class LootChestProtector extends Listener { - // Prevent loot chest destruction - private final Map> loot_break_attempts = new HashMap<>(); - // TODO(legacy): this should become a separate group instead of having - // this boolean. - @ConfigBoolean( - def = true, - desc = "Prevent players from breaking blocks with loot-tables (like treasure chests) when they first attempt to destroy it. They still can break it, but must do so within a short timeframe." - ) - public boolean config_warn_breaking_loot_blocks; + // Prevent loot chest destruction + private final Map> loot_break_attempts = new HashMap<>(); + + // TODO(legacy): this should become a separate group instead of having + // this boolean. + @ConfigBoolean( + def = true, + desc = "Prevent players from breaking blocks with loot-tables (like treasure chests) when they first attempt to destroy it. They still can break it, but must do so within a short timeframe." + ) + public boolean config_warn_breaking_loot_blocks; - @LangMessage - public TranslatedMessage lang_break_loot_block_prevented; + @LangMessage + public TranslatedMessage lang_break_loot_block_prevented; - public LootChestProtector(Context context) { - super(context); - } + public LootChestProtector(Context context) { + super(context); + } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_break_loot_chest(final BlockBreakEvent event) { - if (!config_warn_breaking_loot_blocks) { - return; - } + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_break_loot_chest(final BlockBreakEvent event) { + if (!config_warn_breaking_loot_blocks) { + return; + } - final var state = event.getBlock().getState(false); - if (!(state instanceof Lootable)) { - return; - } + final var state = event.getBlock().getState(false); + if (!(state instanceof Lootable)) { + return; + } - final var lootable = (Lootable) state; - if (!lootable.hasLootTable()) { - return; - } + final var lootable = (Lootable) state; + if (!lootable.hasLootTable()) { + return; + } - final var block = event.getBlock(); - final var player = event.getPlayer(); - var block_attempts = loot_break_attempts.get(block); - final var now = System.currentTimeMillis(); - if (block_attempts != null) { - final var player_attempt_time = block_attempts.get(player.getUniqueId()); - if (player_attempt_time != null) { - final var elapsed = now - player_attempt_time; - if (elapsed > 5000 && elapsed < 30000) { - // Allow - return; - } - } else { - block_attempts.put(player.getUniqueId(), now); - } - } else { - block_attempts = new HashMap(); - block_attempts.put(player.getUniqueId(), now); - loot_break_attempts.put(block, block_attempts); - } + final var block = event.getBlock(); + final var player = event.getPlayer(); + var block_attempts = loot_break_attempts.get(block); + final var now = System.currentTimeMillis(); + if (block_attempts != null) { + final var player_attempt_time = block_attempts.get(player.getUniqueId()); + if (player_attempt_time != null) { + final var elapsed = now - player_attempt_time; + if (elapsed > 5000 && elapsed < 30000) { + // Allow + return; + } + } else { + block_attempts.put(player.getUniqueId(), now); + } + } else { + block_attempts = new HashMap(); + block_attempts.put(player.getUniqueId(), now); + loot_break_attempts.put(block, block_attempts); + } - lang_break_loot_block_prevented.send(player); - event.setCancelled(true); - } + lang_break_loot_block_prevented.send(player); + event.setCancelled(true); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/module/Context.java b/vane-core/src/main/java/org/oddlama/vane/core/module/Context.java index fbb991589..ea5426920 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/module/Context.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/module/Context.java @@ -4,114 +4,114 @@ import java.util.function.Consumer; import org.bukkit.scheduler.BukkitTask; import org.json.JSONObject; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.functional.Consumer1; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; /** - * A ModuleContext is an association to a specific Module and also a - * grouping of config and language variables with a common namespace. + * A ModuleContext is an association to a specific Module and also a grouping of config and language + * variables with a common namespace. */ public interface Context> { - public static String append_yaml_path(String ns1, String ns2, String separator) { - if (ns1.isEmpty()) { - return ns2; - } - return ns1 + separator + ns2; - } + public static String append_yaml_path(String ns1, String ns2, String separator) { + if (ns1.isEmpty()) { + return ns2; + } + return ns1 + separator + ns2; + } - /** create a sub-context namespace */ - public default ModuleContext namespace(String name) { - return new ModuleContext(this, name, null, "."); - } + /** create a sub-context namespace */ + public default ModuleContext namespace(String name) { + return new ModuleContext(this, name, null, "."); + } - /** create a sub-context namespace */ - public default ModuleContext namespace(String name, String description) { - return new ModuleContext(this, name, description, "."); - } + /** create a sub-context namespace */ + public default ModuleContext namespace(String name, String description) { + return new ModuleContext(this, name, description, "."); + } - /** create a sub-context namespace */ - public default ModuleContext namespace(String name, String description, String separator) { - return new ModuleContext(this, name, description, separator); - } + /** create a sub-context namespace */ + public default ModuleContext namespace(String name, String description, String separator) { + return new ModuleContext(this, name, description, separator); + } - /** create a sub-context group */ - public default ModuleGroup group(String group, String description) { - return new ModuleGroup(this, group, description); - } + /** create a sub-context group */ + public default ModuleGroup group(String group, String description) { + return new ModuleGroup(this, group, description); + } - public default ModuleGroup group(String group, String description, boolean default_enabled) { - final var g = new ModuleGroup(this, group, description); - g.config_enabled_def = default_enabled; - return g; - } + public default ModuleGroup group(String group, String description, boolean default_enabled) { + final var g = new ModuleGroup(this, group, description); + g.config_enabled_def = default_enabled; + return g; + } - /** create a sub-context group */ - public default ModuleGroup group_default_disabled(String group, String description) { - final var g = group(group, description); - g.config_enabled_def = false; - return g; - } + /** create a sub-context group */ + public default ModuleGroup group_default_disabled(String group, String description) { + final var g = group(group, description); + g.config_enabled_def = false; + return g; + } - /** - * Compile the given component (processes lang and config definitions) - * and registers it for on_enable, on_disable and on_config_change events. - */ - public void compile(ModuleComponent component); + /** + * Compile the given component (processes lang and config definitions) and registers it for + * on_enable, on_disable and on_config_change events. + */ + public void compile(ModuleComponent component); - public void add_child(Context subcontext); + public void add_child(Context subcontext); - public Context get_context(); + public Context get_context(); - public T get_module(); + public T get_module(); - public String yaml_path(); + public String yaml_path(); - public String variable_yaml_path(String variable); + public String variable_yaml_path(String variable); - public boolean enabled(); + public boolean enabled(); - public void enable(); + public void enable(); - public void disable(); + public void disable(); - public void config_change(); + public void config_change(); - public void generate_resource_pack(final ResourcePackGenerator pack) throws IOException; + public void generate_resource_pack(final ResourcePackGenerator pack) throws IOException; - public void for_each_module_component(final Consumer1> f); + public void for_each_module_component(final Consumer1> f); - public default void on_enable() {} + public default void on_enable() {} - public default void on_disable() {} + public default void on_disable() {} - public default void on_config_change() {} + public default void on_config_change() {} - public default void on_generate_resource_pack(final ResourcePackGenerator pack) throws IOException {} + public default void on_generate_resource_pack(final ResourcePackGenerator pack) throws IOException {} - public default BukkitTask schedule_task_timer(Runnable task, long delay_ticks, long period_ticks) { - return get_module().getServer().getScheduler().runTaskTimer(get_module(), task, delay_ticks, period_ticks); - } + public default BukkitTask schedule_task_timer(Runnable task, long delay_ticks, long period_ticks) { + return get_module().getServer().getScheduler().runTaskTimer(get_module(), task, delay_ticks, period_ticks); + } - public default BukkitTask schedule_task(Runnable task, long delay_ticks) { - return get_module().getServer().getScheduler().runTaskLater(get_module(), task, delay_ticks); - } + public default BukkitTask schedule_task(Runnable task, long delay_ticks) { + return get_module().getServer().getScheduler().runTaskLater(get_module(), task, delay_ticks); + } - public default BukkitTask schedule_next_tick(Runnable task) { - return get_module().getServer().getScheduler().runTask(get_module(), task); - } + public default BukkitTask schedule_next_tick(Runnable task) { + return get_module().getServer().getScheduler().runTask(get_module(), task); + } - public default void add_storage_migration_to(long to, String name, Consumer migrator) { - get_module().persistent_storage_manager.add_migration_to(to, name, migrator); - } + public default void add_storage_migration_to(long to, String name, Consumer migrator) { + get_module().persistent_storage_manager.add_migration_to(to, name, migrator); + } - public default String storage_path_of(String field) { - if (!field.startsWith("storage_")) { - throw new RuntimeException("Configuration fields must be prefixed storage_. This is a bug."); - } - return variable_yaml_path(field.substring("storage_".length())); - } + public default String storage_path_of(String field) { + if (!field.startsWith("storage_")) { + throw new RuntimeException("Configuration fields must be prefixed storage_. This is a bug."); + } + return variable_yaml_path(field.substring("storage_".length())); + } - public default void mark_persistent_storage_dirty() { - get_module().mark_persistent_storage_dirty(); - } + public default void mark_persistent_storage_dirty() { + get_module().mark_persistent_storage_dirty(); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/module/Module.java b/vane-core/src/main/java/org/oddlama/vane/core/module/Module.java index 166947014..e9cf35c54 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/module/Module.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/module/Module.java @@ -2,6 +2,10 @@ import static org.oddlama.vane.util.ResourceList.get_resources; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; @@ -17,7 +21,7 @@ import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.stream.Collectors; - +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import org.bstats.bukkit.Metrics; import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; @@ -48,465 +52,455 @@ import org.oddlama.vane.core.persistent.PersistentStorageManager; import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolManager; - -import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; -import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; -import net.kyori.adventure.text.logger.slf4j.ComponentLogger; - public abstract class Module> extends JavaPlugin implements Context, org.bukkit.event.Listener { - public final VaneModule annotation = getClass().getAnnotation(VaneModule.class); - public Core core; - public Logger log = getLogger(); - public ComponentLogger clog = getComponentLogger(); - private final String namespace = "vane_" + annotation.name().replaceAll("[^a-zA-Z0-9_]", "_"); - - // Managers - public ConfigManager config_manager = new ConfigManager(this); - public LangManager lang_manager = new LangManager(this); - public PersistentStorageManager persistent_storage_manager = new PersistentStorageManager(this); - private boolean persistent_storage_dirty = false; - - // Per module catch-all permissions - public Permission permission_command_catchall_module; - public Random random = new Random(); - - // Permission attachment for console - private List pending_console_permissions = new ArrayList<>(); - public PermissionAttachment console_attachment; - - // Version fields for config, lang, storage - @ConfigVersion - public long config_version; - - @LangVersion - public long lang_version; - - @Persistent - public long storage_version; - - // Base configuration - @ConfigString( - def = "inherit", - desc = "The language for this module. The corresponding language file must be named lang-{lang}.yml. Specifying 'inherit' will load the value set for vane-core.", - metrics = true - ) - public String config_lang; - - @ConfigBoolean( - def = true, - desc = "Enable plugin metrics via bStats. You can opt-out here or via the global bStats configuration. All collected information is completely anonymous and publicly available." - ) - public boolean config_metrics_enabled; - - // Context interface proxy - private ModuleGroup context_group = new ModuleGroup<>( - this, - "", - "The module will only add functionality if this is set to true." - ); - - @Override - public void compile(ModuleComponent component) { - context_group.compile(component); - } - - @Override - public void add_child(Context subcontext) { - if (context_group == null) { - // This happens, when context_group (above) is initialized and calls compile_self(), - // while will try to register it to the parent context (us), but we fake that anyway. - return; - } - context_group.add_child(subcontext); - } - - @Override - public Context get_context() { - return this; - } - - @SuppressWarnings("unchecked") - @Override - public T get_module() { - return (T) this; - } - - @Override - public String yaml_path() { - return ""; - } - - @Override - public String variable_yaml_path(String variable) { - return variable; - } - - @Override - public boolean enabled() { - return context_group.enabled(); - } - - // Callbacks for derived classes - protected void on_load() {} - - public void on_enable() {} - - public void on_disable() {} - - public void on_config_change() {} - - public void on_generate_resource_pack() throws IOException {} - - public final void for_each_module_component(final Consumer1> f) { - context_group.for_each_module_component(f); - } - - // Loot modification - private final Map additional_loot_tables = new HashMap<>(); - - // ProtocolLib - public ProtocolManager protocol_manager; - - // bStats - public Metrics metrics; - - public Module() { - // Get core plugin reference, important for inherited configuration - // and shared state between vane modules - if (this.getName().equals("vane-core")) { - core = (Core) this; - } else { - core = (Core) getServer().getPluginManager().getPlugin("vane-core"); - } - - // Create per module command catch-all permission - permission_command_catchall_module = - new Permission( - "vane." + get_name() + ".commands.*", - "Allow access to all vane-" + get_name() + " commands", - PermissionDefault.FALSE - ); - register_permission(permission_command_catchall_module); - } - - /** - * The namespace used in resource packs - */ - public final String namespace() { - return namespace; - } - - @Override - public final void onLoad() { - // Create data directory - if (!getDataFolder().exists()) { - getDataFolder().mkdirs(); - } - - on_load(); - } - - @Override - public final void onEnable() { - // Create console permission attachment - console_attachment = getServer().getConsoleSender().addAttachment(this); - for (var perm : pending_console_permissions) { - console_attachment.setPermission(perm, true); - } - pending_console_permissions.clear(); - - // Register in core - core.register_module(this); - - // Get protocollib manager - protocol_manager = ProtocolLibrary.getProtocolManager(); - - load_persistent_storage(); - reload_configuration(); - - // Schedule persistent storage saving every minute - schedule_task_timer( - () -> { - if (persistent_storage_dirty) { - save_persistent_storage(); - persistent_storage_dirty = false; - } - }, - 60 * 20, - 60 * 20 - ); - } - - @Override - public void onDisable() { - disable(); - - // Save persistent storage - save_persistent_storage(); - - // Unregister in core - core.unregister_module(this); - } - - @Override - public void enable() { - if (config_metrics_enabled) { - var id = annotation.bstats(); - if (id != -1) { - metrics = new Metrics(this, id); - config_manager.register_metrics(metrics); - } - } - on_enable(); - context_group.enable(); - register_listener(this); - } - - @Override - public void disable() { - unregister_listener(this); - context_group.disable(); - on_disable(); - metrics = null; - } - - @Override - public void config_change() { - on_config_change(); - context_group.config_change(); - } - - @Override - public void generate_resource_pack(final ResourcePackGenerator pack) throws IOException { - // Generate language - final var pattern = Pattern.compile("lang-.*\\.yml"); - Arrays - .stream(getDataFolder().listFiles((d, name) -> pattern.matcher(name).matches())) - .sorted() - .forEach(lang_file -> { - final var yaml = YamlConfiguration.loadConfiguration(lang_file); - try { - lang_manager.generate_resource_pack(pack, yaml, lang_file); - } catch (Exception e) { - throw new RuntimeException( - "Error while generating language for '" + lang_file + "' of module " + get_name(), - e - ); - } - }); - - on_generate_resource_pack(pack); - context_group.generate_resource_pack(pack); - } - - private boolean try_reload_configuration() { - // Generate new file if not existing - final var file = config_manager.standard_file(); - if (!file.exists() && !config_manager.generate_file(file, null)) { - return false; - } - - // Reload automatic variables - return config_manager.reload(file); - } - - private void update_lang_file(String lang_file) { - final var file = new File(getDataFolder(), lang_file); - final var file_version = YamlConfiguration.loadConfiguration(file).getLong("version", -1); - long resource_version = -1; - - final var res = getResource(lang_file); - try (final var reader = new InputStreamReader(res)) { - resource_version = YamlConfiguration.loadConfiguration(reader).getLong("version", -1); - } catch (IOException e) { - log.log(Level.SEVERE, "Error while updating lang file '" + file + "' of module " + get_name(), e); - } - - if (resource_version > file_version) { - try { - Files.copy(getResource(lang_file), file.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - log.log(Level.SEVERE, "Error while copying lang file '" + file + "' of module " + get_name(), e); - } - } - } - - private boolean try_reload_localization() { - // Copy all embedded lang files if their version is newer. - get_resources(getClass(), Pattern.compile("lang-.*\\.yml")).stream().forEach(this::update_lang_file); - - // Get configured language code - var lang_code = config_lang; - if ("inherit".equals(lang_code)) { - lang_code = core.config_lang; - - if (lang_code == null) { - // Core failed to load, so the server will be shutdown anyway. - // Prevent an additional warning by falling back to en. - lang_code = "en"; - } else if ("inherit".equals(lang_code)) { - // Fallback to en in case 'inherit' is used in vane-core. - lang_code = "en"; - } - } - - // Generate new file if not existing - final var file = new File(getDataFolder(), "lang-" + lang_code + ".yml"); - if (!file.exists()) { - log.severe("Missing language file '" + file.getName() + "' for module " + get_name()); - return false; - } - - // Reload automatic variables - return lang_manager.reload(file); - } - - public boolean reload_configuration() { - boolean was_enabled = enabled(); - - if (!try_reload_configuration()) { - // Force stop server, we encountered an invalid config file - log.severe("Invalid plugin configuration. Shutting down."); - getServer().shutdown(); - return false; - } - - // Reload localization - if (!try_reload_localization()) { - // Force stop server, we encountered an invalid lang file - log.severe("Invalid localization file. Shutting down."); - getServer().shutdown(); - return false; - } - - if (was_enabled && !enabled()) { - // Disable plugin if needed - disable(); - } else if (!was_enabled && enabled()) { - // Enable plugin if needed - enable(); - } - - config_change(); - return true; - } - - public File get_persistent_storage_file() { - // Generate new file if not existing - return new File(getDataFolder(), "storage.json"); - } - - public void load_persistent_storage() { - // Load automatic persistent variables - final var file = get_persistent_storage_file(); - if (!persistent_storage_manager.load(file)) { - // Force stop server, we encountered an invalid persistent storage file. - // This prevents further corruption. - log.severe("Invalid persistent storage. Shutting down to prevent further corruption."); - getServer().shutdown(); - } - } - - public void mark_persistent_storage_dirty() { - persistent_storage_dirty = true; - } - - public void save_persistent_storage() { - // Save automatic persistent variables - final var file = get_persistent_storage_file(); - persistent_storage_manager.save(file); - } - - public void register_listener(Listener listener) { - getServer().getPluginManager().registerEvents(listener, this); - } - - public void unregister_listener(Listener listener) { - HandlerList.unregisterAll(listener); - } - - public String get_name() { - return annotation.name(); - } - - public void register_command(Command command) { - LifecycleEventManager manager = this.getLifecycleManager(); - manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { - event.registrar().register(command.get_command(), command.lang_description.str(), command.get_aliases()); - }); - } - - public void unregister_command(Command command) { - var bukkit_command = command.get_bukkit_command(); - getServer().getCommandMap().getKnownCommands().values().remove(bukkit_command); - bukkit_command.unregister(getServer().getCommandMap()); - } - - public void add_console_permission(Permission permission) { - add_console_permission(permission.getName()); - } - - public void add_console_permission(String permission) { - if (console_attachment == null) { - pending_console_permissions.add(permission); - } else { - console_attachment.setPermission(permission, true); - } - } - - public void register_permission(Permission permission) { - try { - getServer().getPluginManager().addPermission(permission); - } catch (IllegalArgumentException e) { - log.log(Level.SEVERE, "Permission '" + permission.getName() + "' was already defined", e); - } - } - - public void unregister_permission(Permission permission) { - getServer().getPluginManager().removePermission(permission); - } - - public LootTable loot_table(final LootTables table) { - return loot_table(table.getKey()); - } - - public List get_offline_players_with_valid_name() { - return Arrays - .stream(getServer().getOfflinePlayers()) - .filter(k -> k.getName() != null) - .collect(Collectors.toList()); - } - - public LootTable loot_table(final NamespacedKey key) { - var additional_loot_table = additional_loot_tables.get(key); - if (additional_loot_table == null) { - additional_loot_table = new LootTable(); - additional_loot_tables.put(key, additional_loot_table); - } - return additional_loot_table; - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void on_module_loot_generate(final LootGenerateEvent event) { - final var loot_table = event.getLootTable(); - // Should never happen because according to the api this is @NotNull, - // yet it happens for some people that copied their world from singleplayer to the server. - if (loot_table == null) { - return; - } - final var additional_loot_table = additional_loot_tables.get(loot_table.getKey()); - if (additional_loot_table == null) { - return; - } - - final var loc = event.getLootContext().getLocation(); - final var local_random = new Random(random.nextInt() - + (loc.getBlockX() & 0xffff << 16) - + (loc.getBlockY() & 0xffff << 32) - + (loc.getBlockZ() & 0xffff << 48)); - additional_loot_table.generate_loot(event.getLoot(), local_random); - } + public final VaneModule annotation = getClass().getAnnotation(VaneModule.class); + public Core core; + public Logger log = getLogger(); + public ComponentLogger clog = getComponentLogger(); + private final String namespace = "vane_" + annotation.name().replaceAll("[^a-zA-Z0-9_]", "_"); + + // Managers + public ConfigManager config_manager = new ConfigManager(this); + public LangManager lang_manager = new LangManager(this); + public PersistentStorageManager persistent_storage_manager = new PersistentStorageManager(this); + private boolean persistent_storage_dirty = false; + + // Per module catch-all permissions + public Permission permission_command_catchall_module; + public Random random = new Random(); + + // Permission attachment for console + private List pending_console_permissions = new ArrayList<>(); + public PermissionAttachment console_attachment; + + // Version fields for config, lang, storage + @ConfigVersion + public long config_version; + + @LangVersion + public long lang_version; + + @Persistent + public long storage_version; + + // Base configuration + @ConfigString( + def = "inherit", + desc = "The language for this module. The corresponding language file must be named lang-{lang}.yml. Specifying 'inherit' will load the value set for vane-core.", + metrics = true + ) + public String config_lang; + + @ConfigBoolean( + def = true, + desc = "Enable plugin metrics via bStats. You can opt-out here or via the global bStats configuration. All collected information is completely anonymous and publicly available." + ) + public boolean config_metrics_enabled; + + // Context interface proxy + private ModuleGroup context_group = new ModuleGroup<>( + this, + "", + "The module will only add functionality if this is set to true." + ); + + @Override + public void compile(ModuleComponent component) { + context_group.compile(component); + } + + @Override + public void add_child(Context subcontext) { + if (context_group == null) { + // This happens, when context_group (above) is initialized and calls compile_self(), + // while will try to register it to the parent context (us), but we fake that anyway. + return; + } + context_group.add_child(subcontext); + } + + @Override + public Context get_context() { + return this; + } + + @SuppressWarnings("unchecked") + @Override + public T get_module() { + return (T) this; + } + + @Override + public String yaml_path() { + return ""; + } + + @Override + public String variable_yaml_path(String variable) { + return variable; + } + + @Override + public boolean enabled() { + return context_group.enabled(); + } + + // Callbacks for derived classes + protected void on_load() {} + + public void on_enable() {} + + public void on_disable() {} + + public void on_config_change() {} + + public void on_generate_resource_pack() throws IOException {} + + public final void for_each_module_component(final Consumer1> f) { + context_group.for_each_module_component(f); + } + + // Loot modification + private final Map additional_loot_tables = new HashMap<>(); + + // ProtocolLib + public ProtocolManager protocol_manager; + + // bStats + public Metrics metrics; + + public Module() { + // Get core plugin reference, important for inherited configuration + // and shared state between vane modules + if (this.getName().equals("vane-core")) { + core = (Core) this; + } else { + core = (Core) getServer().getPluginManager().getPlugin("vane-core"); + } + + // Create per module command catch-all permission + permission_command_catchall_module = new Permission( + "vane." + get_name() + ".commands.*", + "Allow access to all vane-" + get_name() + " commands", + PermissionDefault.FALSE + ); + register_permission(permission_command_catchall_module); + } + + /** The namespace used in resource packs */ + public final String namespace() { + return namespace; + } + + @Override + public final void onLoad() { + // Create data directory + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + + on_load(); + } + + @Override + public final void onEnable() { + // Create console permission attachment + console_attachment = getServer().getConsoleSender().addAttachment(this); + for (var perm : pending_console_permissions) { + console_attachment.setPermission(perm, true); + } + pending_console_permissions.clear(); + + // Register in core + core.register_module(this); + + // Get protocollib manager + protocol_manager = ProtocolLibrary.getProtocolManager(); + + load_persistent_storage(); + reload_configuration(); + + // Schedule persistent storage saving every minute + schedule_task_timer( + () -> { + if (persistent_storage_dirty) { + save_persistent_storage(); + persistent_storage_dirty = false; + } + }, + 60 * 20, + 60 * 20 + ); + } + + @Override + public void onDisable() { + disable(); + + // Save persistent storage + save_persistent_storage(); + + // Unregister in core + core.unregister_module(this); + } + + @Override + public void enable() { + if (config_metrics_enabled) { + var id = annotation.bstats(); + if (id != -1) { + metrics = new Metrics(this, id); + config_manager.register_metrics(metrics); + } + } + on_enable(); + context_group.enable(); + register_listener(this); + } + + @Override + public void disable() { + unregister_listener(this); + context_group.disable(); + on_disable(); + metrics = null; + } + + @Override + public void config_change() { + on_config_change(); + context_group.config_change(); + } + + @Override + public void generate_resource_pack(final ResourcePackGenerator pack) throws IOException { + // Generate language + final var pattern = Pattern.compile("lang-.*\\.yml"); + Arrays.stream(getDataFolder().listFiles((d, name) -> pattern.matcher(name).matches())) + .sorted() + .forEach(lang_file -> { + final var yaml = YamlConfiguration.loadConfiguration(lang_file); + try { + lang_manager.generate_resource_pack(pack, yaml, lang_file); + } catch (Exception e) { + throw new RuntimeException( + "Error while generating language for '" + lang_file + "' of module " + get_name(), + e + ); + } + }); + + on_generate_resource_pack(pack); + context_group.generate_resource_pack(pack); + } + + private boolean try_reload_configuration() { + // Generate new file if not existing + final var file = config_manager.standard_file(); + if (!file.exists() && !config_manager.generate_file(file, null)) { + return false; + } + + // Reload automatic variables + return config_manager.reload(file); + } + + private void update_lang_file(String lang_file) { + final var file = new File(getDataFolder(), lang_file); + final var file_version = YamlConfiguration.loadConfiguration(file).getLong("version", -1); + long resource_version = -1; + + final var res = getResource(lang_file); + try (final var reader = new InputStreamReader(res)) { + resource_version = YamlConfiguration.loadConfiguration(reader).getLong("version", -1); + } catch (IOException e) { + log.log(Level.SEVERE, "Error while updating lang file '" + file + "' of module " + get_name(), e); + } + + if (resource_version > file_version) { + try { + Files.copy(getResource(lang_file), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + log.log(Level.SEVERE, "Error while copying lang file '" + file + "' of module " + get_name(), e); + } + } + } + + private boolean try_reload_localization() { + // Copy all embedded lang files if their version is newer. + get_resources(getClass(), Pattern.compile("lang-.*\\.yml")).stream().forEach(this::update_lang_file); + + // Get configured language code + var lang_code = config_lang; + if ("inherit".equals(lang_code)) { + lang_code = core.config_lang; + + if (lang_code == null) { + // Core failed to load, so the server will be shutdown anyway. + // Prevent an additional warning by falling back to en. + lang_code = "en"; + } else if ("inherit".equals(lang_code)) { + // Fallback to en in case 'inherit' is used in vane-core. + lang_code = "en"; + } + } + + // Generate new file if not existing + final var file = new File(getDataFolder(), "lang-" + lang_code + ".yml"); + if (!file.exists()) { + log.severe("Missing language file '" + file.getName() + "' for module " + get_name()); + return false; + } + + // Reload automatic variables + return lang_manager.reload(file); + } + + public boolean reload_configuration() { + boolean was_enabled = enabled(); + + if (!try_reload_configuration()) { + // Force stop server, we encountered an invalid config file + log.severe("Invalid plugin configuration. Shutting down."); + getServer().shutdown(); + return false; + } + + // Reload localization + if (!try_reload_localization()) { + // Force stop server, we encountered an invalid lang file + log.severe("Invalid localization file. Shutting down."); + getServer().shutdown(); + return false; + } + + if (was_enabled && !enabled()) { + // Disable plugin if needed + disable(); + } else if (!was_enabled && enabled()) { + // Enable plugin if needed + enable(); + } + + config_change(); + return true; + } + + public File get_persistent_storage_file() { + // Generate new file if not existing + return new File(getDataFolder(), "storage.json"); + } + + public void load_persistent_storage() { + // Load automatic persistent variables + final var file = get_persistent_storage_file(); + if (!persistent_storage_manager.load(file)) { + // Force stop server, we encountered an invalid persistent storage file. + // This prevents further corruption. + log.severe("Invalid persistent storage. Shutting down to prevent further corruption."); + getServer().shutdown(); + } + } + + public void mark_persistent_storage_dirty() { + persistent_storage_dirty = true; + } + + public void save_persistent_storage() { + // Save automatic persistent variables + final var file = get_persistent_storage_file(); + persistent_storage_manager.save(file); + } + + public void register_listener(Listener listener) { + getServer().getPluginManager().registerEvents(listener, this); + } + + public void unregister_listener(Listener listener) { + HandlerList.unregisterAll(listener); + } + + public String get_name() { + return annotation.name(); + } + + public void register_command(Command command) { + LifecycleEventManager manager = this.getLifecycleManager(); + manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { + event.registrar().register(command.get_command(), command.lang_description.str(), command.get_aliases()); + }); + } + + public void unregister_command(Command command) { + var bukkit_command = command.get_bukkit_command(); + getServer().getCommandMap().getKnownCommands().values().remove(bukkit_command); + bukkit_command.unregister(getServer().getCommandMap()); + } + + public void add_console_permission(Permission permission) { + add_console_permission(permission.getName()); + } + + public void add_console_permission(String permission) { + if (console_attachment == null) { + pending_console_permissions.add(permission); + } else { + console_attachment.setPermission(permission, true); + } + } + + public void register_permission(Permission permission) { + try { + getServer().getPluginManager().addPermission(permission); + } catch (IllegalArgumentException e) { + log.log(Level.SEVERE, "Permission '" + permission.getName() + "' was already defined", e); + } + } + + public void unregister_permission(Permission permission) { + getServer().getPluginManager().removePermission(permission); + } + + public LootTable loot_table(final LootTables table) { + return loot_table(table.getKey()); + } + + public List get_offline_players_with_valid_name() { + return Arrays.stream(getServer().getOfflinePlayers()) + .filter(k -> k.getName() != null) + .collect(Collectors.toList()); + } + + public LootTable loot_table(final NamespacedKey key) { + var additional_loot_table = additional_loot_tables.get(key); + if (additional_loot_table == null) { + additional_loot_table = new LootTable(); + additional_loot_tables.put(key, additional_loot_table); + } + return additional_loot_table; + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on_module_loot_generate(final LootGenerateEvent event) { + final var loot_table = event.getLootTable(); + // Should never happen because according to the api this is @NotNull, + // yet it happens for some people that copied their world from singleplayer to the server. + if (loot_table == null) { + return; + } + final var additional_loot_table = additional_loot_tables.get(loot_table.getKey()); + if (additional_loot_table == null) { + return; + } + + final var loc = event.getLootContext().getLocation(); + final var local_random = new Random( + random.nextInt() + + (loc.getBlockX() & (0xffff << 16)) + + (loc.getBlockY() & (0xffff << 32)) + + (loc.getBlockZ() & (0xffff << 48)) + ); + additional_loot_table.generate_loot(event.getLoot(), local_random); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleComponent.java b/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleComponent.java index ac7eed39c..7f88da59e 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleComponent.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleComponent.java @@ -9,69 +9,69 @@ public abstract class ModuleComponent> { - private Context context = null; + private Context context = null; - public ModuleComponent(Context context) { - if (context == null) { - // Delay until set_context is called. - return; - } - set_context(context); - } + public ModuleComponent(Context context) { + if (context == null) { + // Delay until set_context is called. + return; + } + set_context(context); + } - public void set_context(Context context) { - if (this.context != null) { - throw new RuntimeException("Cannot replace existing context! This is a bug."); - } - this.context = context; - context.compile(this); - } + public void set_context(Context context) { + if (this.context != null) { + throw new RuntimeException("Cannot replace existing context! This is a bug."); + } + this.context = context; + context.compile(this); + } - public Context get_context() { - return context; - } + public Context get_context() { + return context; + } - public T get_module() { - return context.get_module(); - } + public T get_module() { + return context.get_module(); + } - public boolean enabled() { - return context.enabled(); - } + public boolean enabled() { + return context.enabled(); + } - protected abstract void on_enable(); + protected abstract void on_enable(); - protected abstract void on_disable(); + protected abstract void on_disable(); - protected void on_config_change() {} + protected void on_config_change() {} - protected void on_generate_resource_pack(final ResourcePackGenerator pack) throws IOException {} + protected void on_generate_resource_pack(final ResourcePackGenerator pack) throws IOException {} - public final BukkitTask schedule_task_timer(Runnable task, long delay_ticks, long period_ticks) { - return context.schedule_task_timer(task, delay_ticks, period_ticks); - } + public final BukkitTask schedule_task_timer(Runnable task, long delay_ticks, long period_ticks) { + return context.schedule_task_timer(task, delay_ticks, period_ticks); + } - public final BukkitTask schedule_task(Runnable task, long delay_ticks) { - return context.schedule_task(task, delay_ticks); - } + public final BukkitTask schedule_task(Runnable task, long delay_ticks) { + return context.schedule_task(task, delay_ticks); + } - public final BukkitTask schedule_next_tick(Runnable task) { - return context.schedule_next_tick(task); - } + public final BukkitTask schedule_next_tick(Runnable task) { + return context.schedule_next_tick(task); + } - public final void add_storage_migration_to(long to, String name, Consumer migrator) { - context.add_storage_migration_to(to, name, migrator); - } + public final void add_storage_migration_to(long to, String name, Consumer migrator) { + context.add_storage_migration_to(to, name, migrator); + } - public final String storage_path_of(String field) { - return context.storage_path_of(field); - } + public final String storage_path_of(String field) { + return context.storage_path_of(field); + } - public final void mark_persistent_storage_dirty() { - context.mark_persistent_storage_dirty(); - } + public final void mark_persistent_storage_dirty() { + context.mark_persistent_storage_dirty(); + } - public final NamespacedKey namespaced_key(String value) { - return new NamespacedKey(get_module(), get_context().variable_yaml_path(value)); - } + public final NamespacedKey namespaced_key(String value) { + return new NamespacedKey(get_module(), get_context().variable_yaml_path(value)); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleContext.java b/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleContext.java index 0b63996d6..5a1809435 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleContext.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleContext.java @@ -3,140 +3,140 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; import org.oddlama.vane.core.functional.Consumer1; +import org.oddlama.vane.core.resourcepack.ResourcePackGenerator; /** - * A ModuleContext is an association to a specific Module and also a - * grouping of config and language variables with a common namespace. + * A ModuleContext is an association to a specific Module and also a grouping of config and language + * variables with a common namespace. */ public class ModuleContext> implements Context { - protected Context context; - protected T module; // cache to not generate chains of get_context() - protected String name; - private List> subcontexts = new ArrayList<>(); - private List> components = new ArrayList<>(); - private String description; - private String separator; - - public ModuleContext(Context context, String name, String description, String separator) { - this(context, name, description, separator, true); - } - - public ModuleContext(Context context, String name, String description, String separator, boolean compile_self) { - this.context = context; - this.module = context.get_module(); - this.name = name; - this.description = description; - this.separator = separator; - - if (compile_self) { - compile_self(); - } - } - - @Override - public String yaml_path() { - return Context.append_yaml_path(context.yaml_path(), name, separator); - } - - public String variable_yaml_path(String variable) { - return Context.append_yaml_path(yaml_path(), variable, separator); - } - - @Override - public boolean enabled() { - return context.enabled(); - } - - private void compile_component(Object component) { - module.lang_manager.compile(component, this::variable_yaml_path); - module.config_manager.compile(component, this::variable_yaml_path); - if (description != null) { - module.config_manager.add_section_description(yaml_path(), description); - } - module.persistent_storage_manager.compile(component, this::variable_yaml_path); - } - - protected void compile_self() { - // Compile localization and config fields - compile_component(this); - context.add_child(this); - } - - @Override - public void compile(ModuleComponent component) { - components.add(component); - compile_component(component); - } - - @Override - public void add_child(Context subcontext) { - subcontexts.add(subcontext); - } - - @Override - public Context get_context() { - return context; - } - - @Override - public T get_module() { - return module; - } - - @Override - public void enable() { - on_enable(); - for (var component : components) { - component.on_enable(); - } - for (var subcontext : subcontexts) { - subcontext.enable(); - } - } - - @Override - public void disable() { - for (int i = subcontexts.size() - 1; i >= 0; --i) { - subcontexts.get(i).disable(); - } - for (int i = components.size() - 1; i >= 0; --i) { - components.get(i).on_disable(); - } - on_disable(); - } - - @Override - public void config_change() { - on_config_change(); - for (var component : components) { - component.on_config_change(); - } - for (var subcontext : subcontexts) { - subcontext.config_change(); - } - } - - @Override - public void generate_resource_pack(final ResourcePackGenerator pack) throws IOException { - on_generate_resource_pack(pack); - for (var component : components) { - component.on_generate_resource_pack(pack); - } - for (var subcontext : subcontexts) { - subcontext.generate_resource_pack(pack); - } - } - - @Override - public void for_each_module_component(final Consumer1> f) { - for (var component : components) { - f.apply(component); - } - for (var subcontext : subcontexts) { - subcontext.for_each_module_component(f); - } - } + protected Context context; + protected T module; // cache to not generate chains of get_context() + protected String name; + private List> subcontexts = new ArrayList<>(); + private List> components = new ArrayList<>(); + private String description; + private String separator; + + public ModuleContext(Context context, String name, String description, String separator) { + this(context, name, description, separator, true); + } + + public ModuleContext(Context context, String name, String description, String separator, boolean compile_self) { + this.context = context; + this.module = context.get_module(); + this.name = name; + this.description = description; + this.separator = separator; + + if (compile_self) { + compile_self(); + } + } + + @Override + public String yaml_path() { + return Context.append_yaml_path(context.yaml_path(), name, separator); + } + + public String variable_yaml_path(String variable) { + return Context.append_yaml_path(yaml_path(), variable, separator); + } + + @Override + public boolean enabled() { + return context.enabled(); + } + + private void compile_component(Object component) { + module.lang_manager.compile(component, this::variable_yaml_path); + module.config_manager.compile(component, this::variable_yaml_path); + if (description != null) { + module.config_manager.add_section_description(yaml_path(), description); + } + module.persistent_storage_manager.compile(component, this::variable_yaml_path); + } + + protected void compile_self() { + // Compile localization and config fields + compile_component(this); + context.add_child(this); + } + + @Override + public void compile(ModuleComponent component) { + components.add(component); + compile_component(component); + } + + @Override + public void add_child(Context subcontext) { + subcontexts.add(subcontext); + } + + @Override + public Context get_context() { + return context; + } + + @Override + public T get_module() { + return module; + } + + @Override + public void enable() { + on_enable(); + for (var component : components) { + component.on_enable(); + } + for (var subcontext : subcontexts) { + subcontext.enable(); + } + } + + @Override + public void disable() { + for (int i = subcontexts.size() - 1; i >= 0; --i) { + subcontexts.get(i).disable(); + } + for (int i = components.size() - 1; i >= 0; --i) { + components.get(i).on_disable(); + } + on_disable(); + } + + @Override + public void config_change() { + on_config_change(); + for (var component : components) { + component.on_config_change(); + } + for (var subcontext : subcontexts) { + subcontext.config_change(); + } + } + + @Override + public void generate_resource_pack(final ResourcePackGenerator pack) throws IOException { + on_generate_resource_pack(pack); + for (var component : components) { + component.on_generate_resource_pack(pack); + } + for (var subcontext : subcontexts) { + subcontext.generate_resource_pack(pack); + } + } + + @Override + public void for_each_module_component(final Consumer1> f) { + for (var component : components) { + f.apply(component); + } + for (var subcontext : subcontexts) { + subcontext.for_each_module_component(f); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleGroup.java b/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleGroup.java index c1b4929aa..cba031092 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleGroup.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/module/ModuleGroup.java @@ -3,55 +3,54 @@ import org.oddlama.vane.annotation.config.ConfigBoolean; /** - * A ModuleGroup is a ModuleContext that automatically adds an enabled variable - * with description to the context. If the group is disabled, on_enable() will - * not be called. + * A ModuleGroup is a ModuleContext that automatically adds an enabled variable with description to + * the context. If the group is disabled, on_enable() will not be called. */ public class ModuleGroup> extends ModuleContext { - @ConfigBoolean(def = true, desc = "") // desc is set by #config_enabled_desc() - public boolean config_enabled; - - public boolean config_enabled_def = true; - private String config_enabled_desc; - - public boolean config_enabled_def() { - return config_enabled_def; - } - - public String config_enabled_desc() { - return config_enabled_desc; - } - - public ModuleGroup(Context context, String group, String description) { - this(context, group, description, true); - } - - public ModuleGroup(Context context, String group, String description, boolean compile_self) { - super(context, group, null, ".", false); - this.config_enabled_desc = description; - - if (compile_self) { - compile_self(); - } - } - - @Override - public boolean enabled() { - return config_enabled; - } - - @Override - public void enable() { - if (config_enabled) { - super.enable(); - } - } - - @Override - public void disable() { - if (config_enabled) { - super.disable(); - } - } + @ConfigBoolean(def = true, desc = "") // desc is set by #config_enabled_desc() + public boolean config_enabled; + + public boolean config_enabled_def = true; + private String config_enabled_desc; + + public boolean config_enabled_def() { + return config_enabled_def; + } + + public String config_enabled_desc() { + return config_enabled_desc; + } + + public ModuleGroup(Context context, String group, String description) { + this(context, group, description, true); + } + + public ModuleGroup(Context context, String group, String description, boolean compile_self) { + super(context, group, null, ".", false); + this.config_enabled_desc = description; + + if (compile_self) { + compile_self(); + } + } + + @Override + public boolean enabled() { + return config_enabled; + } + + @Override + public void enable() { + if (config_enabled) { + super.enable(); + } + } + + @Override + public void disable() { + if (config_enabled) { + super.disable(); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentField.java b/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentField.java index c94d870fc..cb63bfc65 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentField.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentField.java @@ -7,43 +7,43 @@ public class PersistentField { - private Object owner; - private Field field; - private String path; - - public PersistentField(Object owner, Field field, Function map_name) { - this.owner = owner; - this.field = field; - this.path = map_name.apply(field.getName().substring("storage_".length())); - - field.setAccessible(true); - } - - public String path() { - return path; - } - - public Object get() { - try { - return field.get(owner); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } - - public void save(JSONObject json) throws IOException { - json.put(path, PersistentSerializer.to_json(field, get())); - } - - public void load(JSONObject json) throws IOException { - if (!json.has(path)) { - throw new IOException("Missing key in persistent storage: '" + path + "'"); - } - - try { - field.set(owner, PersistentSerializer.from_json(field, json.get(path))); - } catch (IllegalAccessException e) { - throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); - } - } + private Object owner; + private Field field; + private String path; + + public PersistentField(Object owner, Field field, Function map_name) { + this.owner = owner; + this.field = field; + this.path = map_name.apply(field.getName().substring("storage_".length())); + + field.setAccessible(true); + } + + public String path() { + return path; + } + + public Object get() { + try { + return field.get(owner); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } + + public void save(JSONObject json) throws IOException { + json.put(path, PersistentSerializer.to_json(field, get())); + } + + public void load(JSONObject json) throws IOException { + if (!json.has(path)) { + throw new IOException("Missing key in persistent storage: '" + path + "'"); + } + + try { + field.set(owner, PersistentSerializer.from_json(field, json.get(path))); + } catch (IllegalAccessException e) { + throw new RuntimeException("Invalid field access on '" + field.getName() + "'. This is a bug."); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentSerializer.java b/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentSerializer.java index 81dbfd1a4..90c6de735 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentSerializer.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentSerializer.java @@ -27,234 +27,234 @@ public class PersistentSerializer { - @FunctionalInterface - public static interface Function { - R apply(T1 t1) throws IOException; - } + @FunctionalInterface + public static interface Function { + R apply(T1 t1) throws IOException; + } - private static Object serialize_namespaced_key(@NotNull final Object o) throws IOException { - return ((NamespacedKey) o).toString(); - } + private static Object serialize_namespaced_key(@NotNull final Object o) throws IOException { + return ((NamespacedKey) o).toString(); + } - private static NamespacedKey deserialize_namespaced_key(@NotNull final Object o) throws IOException { - final var s = ((String) o).split(":"); - if (s.length != 2) { - throw new IOException("Invalid namespaced key '" + s + "'"); - } - return namespaced_key(s[0], s[1]); - } + private static NamespacedKey deserialize_namespaced_key(@NotNull final Object o) throws IOException { + final var s = ((String) o).split(":"); + if (s.length != 2) { + throw new IOException("Invalid namespaced key '" + s + "'"); + } + return namespaced_key(s[0], s[1]); + } - private static Object serialize_lazy_location(@NotNull final Object o) throws IOException { - final var lazy_location = (LazyLocation) o; - final var location = lazy_location.location(); - final var json = new JSONObject(); - json.put("world_id", to_json(UUID.class, lazy_location.world_id())); - json.put("x", to_json(double.class, location.getX())); - json.put("y", to_json(double.class, location.getY())); - json.put("z", to_json(double.class, location.getZ())); - json.put("pitch", to_json(float.class, location.getPitch())); - json.put("yaw", to_json(float.class, location.getYaw())); - return json; - } + private static Object serialize_lazy_location(@NotNull final Object o) throws IOException { + final var lazy_location = (LazyLocation) o; + final var location = lazy_location.location(); + final var json = new JSONObject(); + json.put("world_id", to_json(UUID.class, lazy_location.world_id())); + json.put("x", to_json(double.class, location.getX())); + json.put("y", to_json(double.class, location.getY())); + json.put("z", to_json(double.class, location.getZ())); + json.put("pitch", to_json(float.class, location.getPitch())); + json.put("yaw", to_json(float.class, location.getYaw())); + return json; + } - private static LazyLocation deserialize_lazy_location(@NotNull final Object o) throws IOException { - final var json = (JSONObject) o; - final var world_id = from_json(UUID.class, json.get("world_id")); - final var x = from_json(double.class, json.get("x")); - final var y = from_json(double.class, json.get("y")); - final var z = from_json(double.class, json.get("z")); - final var pitch = from_json(float.class, json.get("pitch")); - final var yaw = from_json(float.class, json.get("yaw")); - return new LazyLocation(world_id, x, y, z, yaw, pitch); - } + private static LazyLocation deserialize_lazy_location(@NotNull final Object o) throws IOException { + final var json = (JSONObject) o; + final var world_id = from_json(UUID.class, json.get("world_id")); + final var x = from_json(double.class, json.get("x")); + final var y = from_json(double.class, json.get("y")); + final var z = from_json(double.class, json.get("z")); + final var pitch = from_json(float.class, json.get("pitch")); + final var yaw = from_json(float.class, json.get("yaw")); + return new LazyLocation(world_id, x, y, z, yaw, pitch); + } - private static Object serialize_lazy_block(@NotNull final Object o) throws IOException { - final var lazy_block = (LazyBlock) o; - final var json = new JSONObject(); - json.put("world_id", to_json(UUID.class, lazy_block.world_id())); - json.put("x", to_json(int.class, lazy_block.x())); - json.put("y", to_json(int.class, lazy_block.y())); - json.put("z", to_json(int.class, lazy_block.z())); - return json; - } + private static Object serialize_lazy_block(@NotNull final Object o) throws IOException { + final var lazy_block = (LazyBlock) o; + final var json = new JSONObject(); + json.put("world_id", to_json(UUID.class, lazy_block.world_id())); + json.put("x", to_json(int.class, lazy_block.x())); + json.put("y", to_json(int.class, lazy_block.y())); + json.put("z", to_json(int.class, lazy_block.z())); + return json; + } - private static LazyBlock deserialize_lazy_block(@NotNull final Object o) throws IOException { - final var json = (JSONObject) o; - final var world_id = from_json(UUID.class, json.get("world_id")); - final var x = from_json(int.class, json.get("x")); - final var y = from_json(int.class, json.get("y")); - final var z = from_json(int.class, json.get("z")); - return new LazyBlock(world_id, x, y, z); - } + private static LazyBlock deserialize_lazy_block(@NotNull final Object o) throws IOException { + final var json = (JSONObject) o; + final var world_id = from_json(UUID.class, json.get("world_id")); + final var x = from_json(int.class, json.get("x")); + final var y = from_json(int.class, json.get("y")); + final var z = from_json(int.class, json.get("z")); + return new LazyBlock(world_id, x, y, z); + } - private static Object serialize_material(@NotNull final Object o) throws IOException { - return to_json(NamespacedKey.class, ((Material) o).getKey()); - } + private static Object serialize_material(@NotNull final Object o) throws IOException { + return to_json(NamespacedKey.class, ((Material) o).getKey()); + } - private static Material deserialize_material(@NotNull final Object o) throws IOException { - return material_from(from_json(NamespacedKey.class, o)); - } + private static Material deserialize_material(@NotNull final Object o) throws IOException { + return material_from(from_json(NamespacedKey.class, o)); + } - private static Object serialize_item_stack(@NotNull final Object o) throws IOException { - return new String(Base64.getEncoder().encode(((ItemStack) o).serializeAsBytes()), StandardCharsets.UTF_8); - } + private static Object serialize_item_stack(@NotNull final Object o) throws IOException { + return new String(Base64.getEncoder().encode(((ItemStack) o).serializeAsBytes()), StandardCharsets.UTF_8); + } - private static ItemStack deserialize_item_stack(@NotNull final Object o) throws IOException { - return ItemStack.deserializeBytes(Base64.getDecoder().decode(((String) o).getBytes(StandardCharsets.UTF_8))); - } + private static ItemStack deserialize_item_stack(@NotNull final Object o) throws IOException { + return ItemStack.deserializeBytes(Base64.getDecoder().decode(((String) o).getBytes(StandardCharsets.UTF_8))); + } - private static boolean is_null(Object o) { - return o == null || o == JSONObject.NULL; - } + private static boolean is_null(Object o) { + return o == null || o == JSONObject.NULL; + } - public static final Map, Function> serializers = new HashMap<>(); - public static final Map, Function> deserializers = new HashMap<>(); + public static final Map, Function> serializers = new HashMap<>(); + public static final Map, Function> deserializers = new HashMap<>(); - static { - // Primitive types - serializers.put(boolean.class, String::valueOf); - serializers.put(char.class, String::valueOf); - serializers.put(double.class, String::valueOf); - serializers.put(float.class, String::valueOf); - serializers.put(int.class, String::valueOf); - serializers.put(long.class, String::valueOf); - serializers.put(Boolean.class, String::valueOf); - serializers.put(Character.class, String::valueOf); - serializers.put(Double.class, String::valueOf); - serializers.put(Float.class, String::valueOf); - serializers.put(Integer.class, String::valueOf); - serializers.put(Long.class, String::valueOf); + static { + // Primitive types + serializers.put(boolean.class, String::valueOf); + serializers.put(char.class, String::valueOf); + serializers.put(double.class, String::valueOf); + serializers.put(float.class, String::valueOf); + serializers.put(int.class, String::valueOf); + serializers.put(long.class, String::valueOf); + serializers.put(Boolean.class, String::valueOf); + serializers.put(Character.class, String::valueOf); + serializers.put(Double.class, String::valueOf); + serializers.put(Float.class, String::valueOf); + serializers.put(Integer.class, String::valueOf); + serializers.put(Long.class, String::valueOf); - deserializers.put(boolean.class, x -> Boolean.parseBoolean((String) x)); - deserializers.put(char.class, x -> ((String) x).charAt(0)); - deserializers.put(double.class, x -> Double.parseDouble((String) x)); - deserializers.put(float.class, x -> Float.parseFloat((String) x)); - deserializers.put(int.class, x -> Integer.parseInt((String) x)); - deserializers.put(long.class, x -> Long.parseLong((String) x)); - deserializers.put(Boolean.class, x -> Boolean.valueOf((String) x)); - deserializers.put(Character.class, x -> ((String) x).charAt(0)); - deserializers.put(Double.class, x -> Double.valueOf((String) x)); - deserializers.put(Float.class, x -> Float.valueOf((String) x)); - deserializers.put(Integer.class, x -> Integer.valueOf((String) x)); - deserializers.put(Long.class, x -> Long.valueOf((String) x)); + deserializers.put(boolean.class, x -> Boolean.parseBoolean((String) x)); + deserializers.put(char.class, x -> ((String) x).charAt(0)); + deserializers.put(double.class, x -> Double.parseDouble((String) x)); + deserializers.put(float.class, x -> Float.parseFloat((String) x)); + deserializers.put(int.class, x -> Integer.parseInt((String) x)); + deserializers.put(long.class, x -> Long.parseLong((String) x)); + deserializers.put(Boolean.class, x -> Boolean.valueOf((String) x)); + deserializers.put(Character.class, x -> ((String) x).charAt(0)); + deserializers.put(Double.class, x -> Double.valueOf((String) x)); + deserializers.put(Float.class, x -> Float.valueOf((String) x)); + deserializers.put(Integer.class, x -> Integer.valueOf((String) x)); + deserializers.put(Long.class, x -> Long.valueOf((String) x)); - // Other types - serializers.put(String.class, x -> x); - deserializers.put(String.class, x -> x); - serializers.put(UUID.class, Object::toString); - deserializers.put(UUID.class, x -> UUID.fromString((String) x)); + // Other types + serializers.put(String.class, x -> x); + deserializers.put(String.class, x -> x); + serializers.put(UUID.class, Object::toString); + deserializers.put(UUID.class, x -> UUID.fromString((String) x)); - // Bukkit types - serializers.put(NamespacedKey.class, PersistentSerializer::serialize_namespaced_key); - deserializers.put(NamespacedKey.class, PersistentSerializer::deserialize_namespaced_key); - serializers.put(LazyLocation.class, PersistentSerializer::serialize_lazy_location); - deserializers.put(LazyLocation.class, PersistentSerializer::deserialize_lazy_location); - serializers.put(LazyBlock.class, PersistentSerializer::serialize_lazy_block); - deserializers.put(LazyBlock.class, PersistentSerializer::deserialize_lazy_block); - serializers.put(Material.class, PersistentSerializer::serialize_material); - deserializers.put(Material.class, PersistentSerializer::deserialize_material); - serializers.put(ItemStack.class, PersistentSerializer::serialize_item_stack); - deserializers.put(ItemStack.class, PersistentSerializer::deserialize_item_stack); - } + // Bukkit types + serializers.put(NamespacedKey.class, PersistentSerializer::serialize_namespaced_key); + deserializers.put(NamespacedKey.class, PersistentSerializer::deserialize_namespaced_key); + serializers.put(LazyLocation.class, PersistentSerializer::serialize_lazy_location); + deserializers.put(LazyLocation.class, PersistentSerializer::deserialize_lazy_location); + serializers.put(LazyBlock.class, PersistentSerializer::serialize_lazy_block); + deserializers.put(LazyBlock.class, PersistentSerializer::deserialize_lazy_block); + serializers.put(Material.class, PersistentSerializer::serialize_material); + deserializers.put(Material.class, PersistentSerializer::deserialize_material); + serializers.put(ItemStack.class, PersistentSerializer::serialize_item_stack); + deserializers.put(ItemStack.class, PersistentSerializer::deserialize_item_stack); + } - public static Object to_json(final Field field, final Object value) throws IOException { - return to_json(field.getGenericType(), value); - } + public static Object to_json(final Field field, final Object value) throws IOException { + return to_json(field.getGenericType(), value); + } - public static Object to_json(final Class cls, final Object value) throws IOException { - final var serializer = serializers.get(cls); - if (serializer == null) { - throw new IOException("Cannot serialize " + cls + ". This is a bug."); - } - if (is_null(value)) { - return JSONObject.NULL; - } - return serializer.apply(value); - } + public static Object to_json(final Class cls, final Object value) throws IOException { + final var serializer = serializers.get(cls); + if (serializer == null) { + throw new IOException("Cannot serialize " + cls + ". This is a bug."); + } + if (is_null(value)) { + return JSONObject.NULL; + } + return serializer.apply(value); + } - public static Object to_json(final Type type, final Object value) throws IOException { - if (type instanceof ParameterizedType) { - final var parameterized_type = (ParameterizedType) type; - final var base_type = parameterized_type.getRawType(); - final var type_args = parameterized_type.getActualTypeArguments(); - if (base_type.equals(Map.class)) { - final var K = (Class) type_args[0]; - final var V = type_args[1]; - final var json = new JSONObject(); - for (final var e : ((Map) value).entrySet()) { - json.put((String) to_json(K, e.getKey()), to_json(V, e.getValue())); - } - return json; - } else if (base_type.equals(Set.class)) { - final var T = type_args[0]; - final var json = new JSONArray(); - for (final var t : (Set) value) { - json.put(to_json(T, t)); - } - return json; - } else if (base_type.equals(List.class)) { - final var T = type_args[0]; - final var json = new JSONArray(); - for (final var t : (List) value) { - json.put(to_json(T, t)); - } - return json; - } else { - throw new IOException("Cannot serialize " + type + ". This is a bug."); - } - } else { - return to_json((Class) type, value); - } - } + public static Object to_json(final Type type, final Object value) throws IOException { + if (type instanceof ParameterizedType) { + final var parameterized_type = (ParameterizedType) type; + final var base_type = parameterized_type.getRawType(); + final var type_args = parameterized_type.getActualTypeArguments(); + if (base_type.equals(Map.class)) { + final var K = (Class) type_args[0]; + final var V = type_args[1]; + final var json = new JSONObject(); + for (final var e : ((Map) value).entrySet()) { + json.put((String) to_json(K, e.getKey()), to_json(V, e.getValue())); + } + return json; + } else if (base_type.equals(Set.class)) { + final var T = type_args[0]; + final var json = new JSONArray(); + for (final var t : (Set) value) { + json.put(to_json(T, t)); + } + return json; + } else if (base_type.equals(List.class)) { + final var T = type_args[0]; + final var json = new JSONArray(); + for (final var t : (List) value) { + json.put(to_json(T, t)); + } + return json; + } else { + throw new IOException("Cannot serialize " + type + ". This is a bug."); + } + } else { + return to_json((Class) type, value); + } + } - public static Object from_json(final Field field, final Object value) throws IOException { - return from_json(field.getGenericType(), value); - } + public static Object from_json(final Field field, final Object value) throws IOException { + return from_json(field.getGenericType(), value); + } - @SuppressWarnings("unchecked") - public static U from_json(final Class cls, final Object value) throws IOException { - final var deserializer = deserializers.get(cls); - if (deserializer == null) { - throw new IOException("Cannot deserialize " + cls + ". This is a bug."); - } - if (is_null(value)) { - return null; - } - return (U) deserializer.apply(value); - } + @SuppressWarnings("unchecked") + public static U from_json(final Class cls, final Object value) throws IOException { + final var deserializer = deserializers.get(cls); + if (deserializer == null) { + throw new IOException("Cannot deserialize " + cls + ". This is a bug."); + } + if (is_null(value)) { + return null; + } + return (U) deserializer.apply(value); + } - public static Object from_json(final Type type, final Object json) throws IOException { - if (type instanceof ParameterizedType) { - final var parameterized_type = (ParameterizedType) type; - final var base_type = parameterized_type.getRawType(); - final var type_args = parameterized_type.getActualTypeArguments(); - if (base_type.equals(Map.class)) { - final var K = (Class) type_args[0]; - final var V = type_args[1]; - final var value = new HashMap(); - for (final var key : ((JSONObject) json).keySet()) { - value.put(from_json(K, key), from_json(V, ((JSONObject) json).get(key))); - } - return value; - } else if (base_type.equals(Set.class)) { - final var T = type_args[0]; - final var value = new HashSet(); - for (final var t : (JSONArray) json) { - value.add(from_json(T, t)); - } - return value; - } else if (base_type.equals(List.class)) { - final var T = type_args[0]; - final var value = new ArrayList(); - for (final var t : (JSONArray) json) { - value.add(from_json(T, t)); - } - return value; - } else { - throw new IOException("Cannot deserialize " + type + ". This is a bug."); - } - } else { - return from_json((Class) type, json); - } - } + public static Object from_json(final Type type, final Object json) throws IOException { + if (type instanceof ParameterizedType) { + final var parameterized_type = (ParameterizedType) type; + final var base_type = parameterized_type.getRawType(); + final var type_args = parameterized_type.getActualTypeArguments(); + if (base_type.equals(Map.class)) { + final var K = (Class) type_args[0]; + final var V = type_args[1]; + final var value = new HashMap(); + for (final var key : ((JSONObject) json).keySet()) { + value.put(from_json(K, key), from_json(V, ((JSONObject) json).get(key))); + } + return value; + } else if (base_type.equals(Set.class)) { + final var T = type_args[0]; + final var value = new HashSet(); + for (final var t : (JSONArray) json) { + value.add(from_json(T, t)); + } + return value; + } else if (base_type.equals(List.class)) { + final var T = type_args[0]; + final var value = new ArrayList(); + for (final var t : (JSONArray) json) { + value.add(from_json(T, t)); + } + return value; + } else { + throw new IOException("Cannot deserialize " + type + ". This is a bug."); + } + } else { + return from_json((Class) type, json); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentStorageManager.java b/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentStorageManager.java index 1a5061d4a..261ef5caa 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentStorageManager.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/persistent/PersistentStorageManager.java @@ -14,204 +14,204 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; -import java.util.stream.Collectors; import org.json.JSONObject; import org.oddlama.vane.annotation.persistent.Persistent; import org.oddlama.vane.core.module.Module; public class PersistentStorageManager { - public class Migration { - - public long to; - public String name; - public Consumer migrator; - - public Migration(long to, String name, Consumer migrator) { - this.to = to; - this.name = name; - this.migrator = migrator; - } - } - - private List persistent_fields = new ArrayList<>(); - private List migrations = new ArrayList<>(); - Module module; - boolean is_loaded = false; - - public PersistentStorageManager(Module module) { - this.module = module; - compile(module, s -> s); - } - - private boolean has_persistent_annotation(Field field) { - for (var a : field.getAnnotations()) { - if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.persistent.Persistent")) { - return true; - } - } - return false; - } - - private void assert_field_prefix(Field field) { - if (!field.getName().startsWith("storage_")) { - throw new RuntimeException("Configuration fields must be prefixed storage_. This is a bug."); - } - } - - private PersistentField compile_field(Object owner, Field field, Function map_name) { - assert_field_prefix(field); - - // Get the annotation - Annotation annotation = null; - for (var a : field.getAnnotations()) { - if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.persistent.Persistent")) { - if (annotation == null) { - annotation = a; - } else { - throw new RuntimeException("Persistent fields must have exactly one @Persistent annotation."); - } - } - } - assert annotation != null; - final var atype = annotation.annotationType(); - - // Return a correct wrapper object - if (atype.equals(Persistent.class)) { - return new PersistentField(owner, field, map_name); - } else { - throw new RuntimeException("Missing PersistentField handler for @" + atype.getName() + ". This is a bug."); - } - } - - @SuppressWarnings("unchecked") - public void compile(Object owner, Function map_name) { - // Compile all annotated fields - persistent_fields.addAll( - getAllFields(owner.getClass()) - .stream() - .filter(this::has_persistent_annotation) - .map(f -> compile_field(owner, f, map_name)).toList() - ); - } - - public void add_migration_to(long to, String name, Consumer migrator) { - migrations.add(new Migration(to, name, migrator)); - } - - @SuppressWarnings("unchecked") - public boolean load(File file) { - if (!file.exists() && is_loaded) { - module.log.severe("Cannot reload persistent storage from nonexistent file '" + file.getName() + "'"); - return false; - } - - // Reset loaded status - is_loaded = false; - - final JSONObject json; - if (file.exists()) { - // Open file and read json - try { - json = new JSONObject(Files.readString(file.toPath(), StandardCharsets.UTF_8)); - } catch (IOException e) { - module.log.severe("error while loading persistent data from '" + file.getName() + "':"); - module.log.severe(e.getMessage()); - return false; - } - } else { - json = new JSONObject(); - } - - // Check version and migrate if necessary - final var version_path = module.storage_path_of("storage_version"); - final var version = Long.parseLong(json.optString(version_path, "0")); - final var needed_version = module.annotation.storage_version(); - if (version != needed_version && migrations.size() > 0) { - module.log.info("Persistent storage is out of date."); - module.log.info("§dMigrating storage from version §b" + version + " → " + needed_version + "§d:"); - - // Sort migrations by target version, - // then apply new migrations in order. - migrations - .stream() - .filter(m -> m.to >= version) - .sorted((a, b) -> Long.compare(a.to, b.to)) - .forEach(m -> { - module.log.info(" → §b" + m.to + "§r : Applying migration '§a" + m.name + "§r'"); - m.migrator.accept(json); - }); - } - - // Overwrite new version - json.put(version_path, String.valueOf(needed_version)); - - try { - for (final var f : persistent_fields) { - // If we have just initialized a new json object, we only load values that - // have defined keys (e.g., from initialization migrations) - if (version == 0 && !json.has(f.path())) { - continue; - } - - f.load(json); - } - } catch (IOException e) { - module.log.log(Level.SEVERE, "error while loading persistent variables from '" + file.getName() + "'", e); - return false; - } - - is_loaded = true; - return true; - } - - public void save(File file) { - if (!is_loaded) { - // Don't save if never loaded or a previous load was faulty. - return; - } - - // Create json with whole content - final var json = new JSONObject(); - - // Save version - final var version_path = module.storage_path_of("storage_version"); - json.put(version_path, String.valueOf(module.annotation.storage_version())); - - // Save fields - for (final var f : persistent_fields) { - try { - f.save(json); - } catch (IOException e) { - module.log.log(Level.SEVERE, "error while serializing persistent data!", e); - } - } - - // Save to tmp file, then move atomically to prevent corruption. - final var tmp_file = new File(file.getAbsolutePath() + ".tmp"); - try { - Files.writeString(tmp_file.toPath(), json.toString()); - } catch (IOException e) { - module.log.log(Level.SEVERE, "error while saving persistent data to temporary file!", e); - return; - } - - // Move atomically to prevent corruption. - try { - Files.move( - tmp_file.toPath(), - file.toPath(), - StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.ATOMIC_MOVE - ); - } catch (IOException e) { - module.log.log( - Level.SEVERE, - "error while atomically replacing '" + - file + - "' with temporary file (very recent changes might be lost)!", - e - ); - } - } + public class Migration { + + public long to; + public String name; + public Consumer migrator; + + public Migration(long to, String name, Consumer migrator) { + this.to = to; + this.name = name; + this.migrator = migrator; + } + } + + private List persistent_fields = new ArrayList<>(); + private List migrations = new ArrayList<>(); + Module module; + boolean is_loaded = false; + + public PersistentStorageManager(Module module) { + this.module = module; + compile(module, s -> s); + } + + private boolean has_persistent_annotation(Field field) { + for (var a : field.getAnnotations()) { + if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.persistent.Persistent")) { + return true; + } + } + return false; + } + + private void assert_field_prefix(Field field) { + if (!field.getName().startsWith("storage_")) { + throw new RuntimeException("Configuration fields must be prefixed storage_. This is a bug."); + } + } + + private PersistentField compile_field(Object owner, Field field, Function map_name) { + assert_field_prefix(field); + + // Get the annotation + Annotation annotation = null; + for (var a : field.getAnnotations()) { + if (a.annotationType().getName().startsWith("org.oddlama.vane.annotation.persistent.Persistent")) { + if (annotation == null) { + annotation = a; + } else { + throw new RuntimeException("Persistent fields must have exactly one @Persistent annotation."); + } + } + } + assert annotation != null; + final var atype = annotation.annotationType(); + + // Return a correct wrapper object + if (atype.equals(Persistent.class)) { + return new PersistentField(owner, field, map_name); + } else { + throw new RuntimeException("Missing PersistentField handler for @" + atype.getName() + ". This is a bug."); + } + } + + @SuppressWarnings("unchecked") + public void compile(Object owner, Function map_name) { + // Compile all annotated fields + persistent_fields.addAll( + getAllFields(owner.getClass()) + .stream() + .filter(this::has_persistent_annotation) + .map(f -> compile_field(owner, f, map_name)) + .toList() + ); + } + + public void add_migration_to(long to, String name, Consumer migrator) { + migrations.add(new Migration(to, name, migrator)); + } + + @SuppressWarnings("unchecked") + public boolean load(File file) { + if (!file.exists() && is_loaded) { + module.log.severe("Cannot reload persistent storage from nonexistent file '" + file.getName() + "'"); + return false; + } + + // Reset loaded status + is_loaded = false; + + final JSONObject json; + if (file.exists()) { + // Open file and read json + try { + json = new JSONObject(Files.readString(file.toPath(), StandardCharsets.UTF_8)); + } catch (IOException e) { + module.log.severe("error while loading persistent data from '" + file.getName() + "':"); + module.log.severe(e.getMessage()); + return false; + } + } else { + json = new JSONObject(); + } + + // Check version and migrate if necessary + final var version_path = module.storage_path_of("storage_version"); + final var version = Long.parseLong(json.optString(version_path, "0")); + final var needed_version = module.annotation.storage_version(); + if (version != needed_version && migrations.size() > 0) { + module.log.info("Persistent storage is out of date."); + module.log.info("§dMigrating storage from version §b" + version + " → " + needed_version + "§d:"); + + // Sort migrations by target version, + // then apply new migrations in order. + migrations + .stream() + .filter(m -> m.to >= version) + .sorted((a, b) -> Long.compare(a.to, b.to)) + .forEach(m -> { + module.log.info(" → §b" + m.to + "§r : Applying migration '§a" + m.name + "§r'"); + m.migrator.accept(json); + }); + } + + // Overwrite new version + json.put(version_path, String.valueOf(needed_version)); + + try { + for (final var f : persistent_fields) { + // If we have just initialized a new json object, we only load values that + // have defined keys (e.g., from initialization migrations) + if (version == 0 && !json.has(f.path())) { + continue; + } + + f.load(json); + } + } catch (IOException e) { + module.log.log(Level.SEVERE, "error while loading persistent variables from '" + file.getName() + "'", e); + return false; + } + + is_loaded = true; + return true; + } + + public void save(File file) { + if (!is_loaded) { + // Don't save if never loaded or a previous load was faulty. + return; + } + + // Create json with whole content + final var json = new JSONObject(); + + // Save version + final var version_path = module.storage_path_of("storage_version"); + json.put(version_path, String.valueOf(module.annotation.storage_version())); + + // Save fields + for (final var f : persistent_fields) { + try { + f.save(json); + } catch (IOException e) { + module.log.log(Level.SEVERE, "error while serializing persistent data!", e); + } + } + + // Save to tmp file, then move atomically to prevent corruption. + final var tmp_file = new File(file.getAbsolutePath() + ".tmp"); + try { + Files.writeString(tmp_file.toPath(), json.toString()); + } catch (IOException e) { + module.log.log(Level.SEVERE, "error while saving persistent data to temporary file!", e); + return; + } + + // Move atomically to prevent corruption. + try { + Files.move( + tmp_file.toPath(), + file.toPath(), + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE + ); + } catch (IOException e) { + module.log.log( + Level.SEVERE, + "error while atomically replacing '" + + file + + "' with temporary file (very recent changes might be lost)!", + e + ); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/CustomResourcePackConfig.java b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/CustomResourcePackConfig.java index f55acb6c8..baec76ed8 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/CustomResourcePackConfig.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/CustomResourcePackConfig.java @@ -7,31 +7,31 @@ public class CustomResourcePackConfig extends ModuleComponent { - @ConfigString( - def = "https://your-server.tld/path/to/pack.zip", - desc = "URL to an resource pack. Will request players to use the specified resource pack. [as of 1.16.2] Beware that the minecraft client currently has issues with webservers that serve resource packs via https and don't allow ssl3. This protocol is considered insecure and therefore should NOT be used. To workaround this issue, you should host the file in a http context. Using http is not a security issue, as the file will be verified via its sha1 sum by the client." - ) - public String config_url; - - @ConfigString(def = "", desc = "Resource pack SHA-1 sum. Required to verify resource pack integrity.") - public String config_sha1; - - @ConfigString(def = "", desc = "Resource pack UUID.") - public String config_uuid; - - public CustomResourcePackConfig(Context context) { - super( - context.group( - "custom_resource_pack", - "If this is not enabled, vane will automatically distribute the official vane resource pack. By enabling this option, you can have vane distribute a custom resource pack (with the given url and sha1) instead of the official vane resource pack. Use this option only if you either want to distribute another resource pack (you will need to merge the vane resources by hand!) or self-host the vane resource pack (generated via `/vane generate_resource_pack`). The latter is necessary when you make adjustments to the language files of vane. For more information on this, see the wiki (https://github.com/oddlama/vane/wiki/Creating-a-Translation).", - false - ) - ); - } - - @Override - protected void on_enable() {} - - @Override - protected void on_disable() {} + @ConfigString( + def = "https://your-server.tld/path/to/pack.zip", + desc = "URL to an resource pack. Will request players to use the specified resource pack. [as of 1.16.2] Beware that the minecraft client currently has issues with webservers that serve resource packs via https and don't allow ssl3. This protocol is considered insecure and therefore should NOT be used. To workaround this issue, you should host the file in a http context. Using http is not a security issue, as the file will be verified via its sha1 sum by the client." + ) + public String config_url; + + @ConfigString(def = "", desc = "Resource pack SHA-1 sum. Required to verify resource pack integrity.") + public String config_sha1; + + @ConfigString(def = "", desc = "Resource pack UUID.") + public String config_uuid; + + public CustomResourcePackConfig(Context context) { + super( + context.group( + "custom_resource_pack", + "If this is not enabled, vane will automatically distribute the official vane resource pack. By enabling this option, you can have vane distribute a custom resource pack (with the given url and sha1) instead of the official vane resource pack. Use this option only if you either want to distribute another resource pack (you will need to merge the vane resources by hand!) or self-host the vane resource pack (generated via `/vane generate_resource_pack`). The latter is necessary when you make adjustments to the language files of vane. For more information on this, see the wiki (https://github.com/oddlama/vane/wiki/Creating-a-Translation).", + false + ) + ); + } + + @Override + protected void on_enable() {} + + @Override + protected void on_disable() {} } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/PlayerMessageDelayer.java b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/PlayerMessageDelayer.java index 161269379..80b906a3c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/PlayerMessageDelayer.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/PlayerMessageDelayer.java @@ -23,174 +23,179 @@ public class PlayerMessageDelayer extends Listener { - private Adapter adapter; - private final Map> message_queues = new HashMap<>(); - private final Map message_queue_start_time = new HashMap<>(); - - // Force-stop delaying after 30 seconds. - private static final long message_delaying_timeout = 30000; - - public PlayerMessageDelayer(Context context) { - super( - context.group( - "message_delaying", - "Enable delaying messages to players until their resource pack is fully loaded. This prevents display of untranslated chat messages.")); - } - - @Override - protected void on_enable() { - adapter = new Adapter(); - get_module().protocol_manager.addPacketListener(adapter); - - super.on_enable(); - - // Check for message delaying timeouts every 5 seconds. - schedule_task_timer( - () -> check_message_delay_timeout(), - 5 * 20, - 5 * 20); - } - - private void check_message_delay_timeout() { - final var now = System.currentTimeMillis(); - for (final var uuid : new HashMap<>(message_queue_start_time).keySet()) { - final var start_time = message_queue_start_time.get(uuid); - if (now - start_time > message_delaying_timeout) { - final var offline_player = get_module().getServer().getOfflinePlayer(uuid); - if (!offline_player.isOnline()) { - stop_queueing(uuid); - continue; - } - - final var player = offline_player.getPlayer(); - relay_messages_and_stop_queueing(player); - get_module().log.warning( - "Force stopped delaying messages to player '" + - player.getName() + - "' after their client didn't report any status after " + - message_delaying_timeout + - " ms. If this message appears again after they reconnect, it might be a configuration issue."); - player.sendMessage( - "Your client failed to report that the resource pack has been applied after " + - message_delaying_timeout + - " ms. If you encounter text formatting issues, please reconnect. If this message appears again, it might be a plugin configuration issue."); - } - } - } - - @Override - protected void on_disable() { - get_module().protocol_manager.removePacketListener(adapter); - super.on_disable(); - } - - private void start_queueing(UUID uuid) { - message_queues.put(uuid, new ArrayList()); - message_queue_start_time.put(uuid, System.currentTimeMillis()); - } - - private List stop_queueing(UUID uuid) { - message_queue_start_time.remove(uuid); - return message_queues.remove(uuid); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_pre_login(final AsyncPlayerPreLoginEvent event) { - if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - return; - } - - // Begin queueing messages for the player - start_queueing(event.getUniqueId()); - } - - private void relay_messages_and_stop_queueing(final Player player) { - // Send delayed messages, which would otherwise be displayed in untranslated - // form - // as the resource pack is only now fully loaded - final var queue = stop_queueing(player.getUniqueId()); - if (queue == null) { - return; - } - for (final var packet : queue) { - get_module().protocol_manager.sendServerPacket(player, packet); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_kick(final PlayerKickEvent event) { - stop_queueing(event.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_quit(final PlayerQuitEvent event) { - stop_queueing(event.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) - public void on_player_status(final PlayerResourcePackStatusEvent event) { - switch (event.getStatus()) { - case ACCEPTED: - case DOWNLOADED: - // Wait until the next status. - return; - case DECLINED: - case FAILED_DOWNLOAD: - case SUCCESSFULLY_LOADED: - case INVALID_URL: - case FAILED_RELOAD: - case DISCARDED: - // Disable queueing - break; - } - - final var player = event.getPlayer(); - relay_messages_and_stop_queueing(player); - - switch (event.getStatus()) { - case DECLINED: - get_module().log.info( - "The player " + - player.getName() + - " rejected the resource pack. This will cause client-side issues with formatted text for them."); - player.sendMessage( - "You have rejected the resource pack. This will cause major issues with formatted text and custom items."); - break; - case FAILED_DOWNLOAD: - get_module().log.info( - "The resource pack download for player " + - player.getName() + - " failed. This will cause client-side issues with formatted text for them."); - player.sendMessage( - "Your resource pack download failed. Please reconnect to retry, otherwise this will cause major issues with formatted text and custom items."); - break; - default: - break; - } - } - - public class Adapter extends PacketAdapter { - - public Adapter() { - super( - PlayerMessageDelayer.this.get_module(), - ListenerPriority.HIGHEST, - PacketType.Play.Server.SYSTEM_CHAT); - } - - @Override - public void onPacketSending(final PacketEvent event) { - if (event.getPacketType() != PacketType.Play.Server.SYSTEM_CHAT) { - return; - } - - final var queue = message_queues.get(event.getPlayer().getUniqueId()); - if (queue == null) { - return; - } - - queue.add(event.getPacket()); - event.setCancelled(true); - } - } + private Adapter adapter; + private final Map> message_queues = new HashMap<>(); + private final Map message_queue_start_time = new HashMap<>(); + + // Force-stop delaying after 30 seconds. + private static final long message_delaying_timeout = 30000; + + public PlayerMessageDelayer(Context context) { + super( + context.group( + "message_delaying", + "Enable delaying messages to players until their resource pack is fully loaded. This prevents display of untranslated chat messages." + ) + ); + } + + @Override + protected void on_enable() { + adapter = new Adapter(); + get_module().protocol_manager.addPacketListener(adapter); + + super.on_enable(); + + // Check for message delaying timeouts every 5 seconds. + schedule_task_timer(() -> check_message_delay_timeout(), 5 * 20, 5 * 20); + } + + private void check_message_delay_timeout() { + final var now = System.currentTimeMillis(); + for (final var uuid : new HashMap<>(message_queue_start_time).keySet()) { + final var start_time = message_queue_start_time.get(uuid); + if (now - start_time > message_delaying_timeout) { + final var offline_player = get_module().getServer().getOfflinePlayer(uuid); + if (!offline_player.isOnline()) { + stop_queueing(uuid); + continue; + } + + final var player = offline_player.getPlayer(); + relay_messages_and_stop_queueing(player); + get_module() + .log.warning( + "Force stopped delaying messages to player '" + + player.getName() + + "' after their client didn't report any status after " + + message_delaying_timeout + + " ms. If this message appears again after they reconnect, it might be a configuration issue." + ); + player.sendMessage( + "Your client failed to report that the resource pack has been applied after " + + message_delaying_timeout + + " ms. If you encounter text formatting issues, please reconnect. If this message appears again, it might be a plugin configuration issue." + ); + } + } + } + + @Override + protected void on_disable() { + get_module().protocol_manager.removePacketListener(adapter); + super.on_disable(); + } + + private void start_queueing(UUID uuid) { + message_queues.put(uuid, new ArrayList()); + message_queue_start_time.put(uuid, System.currentTimeMillis()); + } + + private List stop_queueing(UUID uuid) { + message_queue_start_time.remove(uuid); + return message_queues.remove(uuid); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_pre_login(final AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + return; + } + + // Begin queueing messages for the player + start_queueing(event.getUniqueId()); + } + + private void relay_messages_and_stop_queueing(final Player player) { + // Send delayed messages, which would otherwise be displayed in untranslated + // form + // as the resource pack is only now fully loaded + final var queue = stop_queueing(player.getUniqueId()); + if (queue == null) { + return; + } + for (final var packet : queue) { + get_module().protocol_manager.sendServerPacket(player, packet); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_kick(final PlayerKickEvent event) { + stop_queueing(event.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_quit(final PlayerQuitEvent event) { + stop_queueing(event.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) + public void on_player_status(final PlayerResourcePackStatusEvent event) { + switch (event.getStatus()) { + case ACCEPTED: + case DOWNLOADED: + // Wait until the next status. + return; + case DECLINED: + case FAILED_DOWNLOAD: + case SUCCESSFULLY_LOADED: + case INVALID_URL: + case FAILED_RELOAD: + case DISCARDED: + // Disable queueing + break; + } + + final var player = event.getPlayer(); + relay_messages_and_stop_queueing(player); + + switch (event.getStatus()) { + case DECLINED: + get_module() + .log.info( + "The player " + + player.getName() + + " rejected the resource pack. This will cause client-side issues with formatted text for them." + ); + player.sendMessage( + "You have rejected the resource pack. This will cause major issues with formatted text and custom items." + ); + break; + case FAILED_DOWNLOAD: + get_module() + .log.info( + "The resource pack download for player " + + player.getName() + + " failed. This will cause client-side issues with formatted text for them." + ); + player.sendMessage( + "Your resource pack download failed. Please reconnect to retry, otherwise this will cause major issues with formatted text and custom items." + ); + break; + default: + break; + } + } + + public class Adapter extends PacketAdapter { + + public Adapter() { + super(PlayerMessageDelayer.this.get_module(), ListenerPriority.HIGHEST, PacketType.Play.Server.SYSTEM_CHAT); + } + + @Override + public void onPacketSending(final PacketEvent event) { + if (event.getPacketType() != PacketType.Play.Server.SYSTEM_CHAT) { + return; + } + + final var queue = message_queues.get(event.getPlayer().getUniqueId()); + if (queue == null) { + return; + } + + queue.add(event.getPacket()); + event.setCancelled(true); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDevServer.java b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDevServer.java index 6686aef2c..8160825e3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDevServer.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDevServer.java @@ -13,54 +13,54 @@ public class ResourcePackDevServer implements HttpHandler { - private final ResourcePackDistributor resource_pack_distributor; - private final File file; + private final ResourcePackDistributor resource_pack_distributor; + private final File file; - public ResourcePackDevServer(ResourcePackDistributor resource_pack_distributor, File file) { - this.resource_pack_distributor = resource_pack_distributor; - this.file = file; - } + public ResourcePackDevServer(ResourcePackDistributor resource_pack_distributor, File file) { + this.resource_pack_distributor = resource_pack_distributor; + this.file = file; + } - @SuppressWarnings({"deprecation", "UnstableApiUsage"}) - public void serve() { - try { - final HttpServer httpServer = HttpServer.create(new InetSocketAddress(9000), 0); - var hash = com.google.common.io.Files.asByteSource(this.file).hash(Hashing.sha1()); - resource_pack_distributor.sha1 = hash.toString(); - resource_pack_distributor.url = "http://localhost:9000/vane-resource-pack.zip"; + @SuppressWarnings({ "deprecation", "UnstableApiUsage" }) + public void serve() { + try { + final HttpServer httpServer = HttpServer.create(new InetSocketAddress(9000), 0); + var hash = com.google.common.io.Files.asByteSource(this.file).hash(Hashing.sha1()); + resource_pack_distributor.sha1 = hash.toString(); + resource_pack_distributor.url = "http://localhost:9000/vane-resource-pack.zip"; - httpServer.createContext("/", this); - httpServer.setExecutor(null); - httpServer.start(); - } catch (IOException e) { - e.printStackTrace(); - } - } + httpServer.createContext("/", this); + httpServer.setExecutor(null); + httpServer.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } - public void handle(HttpExchange he) throws IOException { - String method = he.getRequestMethod(); - if (!("HEAD".equals(method) || "GET".equals(method))) { - he.sendResponseHeaders(501, -1); - return; - } + public void handle(HttpExchange he) throws IOException { + String method = he.getRequestMethod(); + if (!("HEAD".equals(method) || "GET".equals(method))) { + he.sendResponseHeaders(501, -1); + return; + } - FileInputStream fis; - try { - fis = new FileInputStream(file); - } catch (FileNotFoundException e) { - he.sendResponseHeaders(404, -1); - return; - } + FileInputStream fis; + try { + fis = new FileInputStream(file); + } catch (FileNotFoundException e) { + he.sendResponseHeaders(404, -1); + return; + } - he.getResponseHeaders().set("Content-Type", "application/zip"); - if ("GET".equals(method)) { - he.sendResponseHeaders(200, file.length()); - OutputStream os = he.getResponseBody(); - fis.transferTo(os); - os.close(); - } else { - he.sendResponseHeaders(200, -1); - } - fis.close(); - } + he.getResponseHeaders().set("Content-Type", "application/zip"); + if ("GET".equals(method)) { + he.sendResponseHeaders(200, file.length()); + OutputStream os = he.getResponseBody(); + fis.transferTo(os); + os.close(); + } else { + he.sendResponseHeaders(200, -1); + } + fis.close(); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDistributor.java b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDistributor.java index 8be6056f8..7f34227a5 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDistributor.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackDistributor.java @@ -1,12 +1,15 @@ package org.oddlama.vane.core.resourcepack; +import com.google.common.hash.Hashing; +import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Properties; import java.util.UUID; - +import net.kyori.adventure.resource.ResourcePackInfo; +import net.kyori.adventure.resource.ResourcePackRequest; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -23,188 +26,186 @@ import org.oddlama.vane.core.module.ModuleGroup; import org.oddlama.vane.util.Nms; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; - -import net.kyori.adventure.resource.ResourcePackInfo; -import net.kyori.adventure.resource.ResourcePackRequest; -import net.kyori.adventure.text.Component; - public class ResourcePackDistributor extends Listener { - // Assume debug environment if both add-plugin and vane-debug are defined, until run-paper adds a better way. - // https://github.com/jpenilla/run-paper/issues/14 - private static final boolean localDev = - Nms.server_handle().options.hasArgument("add-plugin") && Boolean.getBoolean("disable.watchdog"); - - @ConfigBoolean( - def = true, - desc = "Kick players if they deny to use the specified resource pack (if set). Individual players can be exempt from this rule by giving them the permission 'vane.core.resource_pack.bypass'." - ) - public boolean config_force; - - @LangMessage - public TranslatedMessage lang_declined; - - @LangMessage - public TranslatedMessage lang_download_failed; - - public String url = null; - public String sha1 = null; - public UUID uuid = null; - public int counter = 0; - - // The permission to bypass the resource pack - public final Permission bypass_permission; - - public CustomResourcePackConfig custom_resource_pack_config; - public PlayerMessageDelayer player_message_delayer; - private ResourcePackFileWatcher file_watcher; - private ResourcePackDevServer dev_server; - - public ResourcePackDistributor(Context context) { - super(context.group("resource_pack", "Enable resource pack distribution.")); - // Delay messages if this the distributor is active. - custom_resource_pack_config = new CustomResourcePackConfig(get_context()); - // Delay messages if this the distributor is active. - player_message_delayer = new PlayerMessageDelayer(get_context()); - - // Register bypass permission - bypass_permission = - new Permission( - "vane." + get_module().get_name() + ".resource_pack.bypass", - "Allows bypassing an enforced resource pack", - PermissionDefault.FALSE - ); - get_module().register_permission(bypass_permission); - } - - @Override - public void on_enable() { - if (localDev) { - try { - File pack_output = new File("vane-resource-pack.zip"); - if(!pack_output.exists()) { - get_module().log.info("Resource Pack Missing, first run? Generating resource pack."); - pack_output = get_module().generate_resource_pack(); - } - file_watcher = new ResourcePackFileWatcher(this, pack_output); - dev_server = new ResourcePackDevServer(this, pack_output); - dev_server.serve(); - file_watcher.watch_for_changes(); - } catch (IOException | InterruptedException ignored) { - ignored.printStackTrace(); - } - - get_module().log.info("Setting up dev lazy server"); - } else if (((ModuleGroup) custom_resource_pack_config.get_context()).config_enabled) { - get_module().log.info("Serving custom resource pack"); - url = custom_resource_pack_config.config_url; - sha1 = custom_resource_pack_config.config_sha1; - uuid = UUID.fromString(custom_resource_pack_config.config_uuid); - } else { - get_module().log.info("Serving official vane resource pack"); - try { - Properties properties = new Properties(); - properties.load(Core.class.getResourceAsStream("/vane-core.properties")); - url = properties.getProperty("resource_pack_url"); - sha1 = properties.getProperty("resource_pack_sha1"); - uuid = UUID.fromString(properties.getProperty("resource_pack_uuid")); - } catch (IOException e) { - get_module().log.severe("Could not load official resource pack sha1 from included properties file"); - url = ""; - sha1 = ""; - uuid = UUID.randomUUID(); - } - } - - // Check sha1 sum validity - if (sha1.length() != 40) { - get_module() - .log.warning( - "Invalid resource pack SHA-1 sum '" + - sha1 + - "', should be 40 characters long but has " + - sha1.length() + - " characters" - ); - get_module().log.warning("Disabling resource pack serving and message delaying"); - - // Disable resource pack - url = ""; - // Prevent subcontexts from being enabling - // FIXME this can be coded more cleanly. We need a way - // to process config changes _before_ the module is enabled. - // like on_config_change_pre_enable(), where we can override - // the context group enable state. - ((ModuleGroup) player_message_delayer.get_context()).config_enabled = false; - } - - // Propagate enable after determining whether the player message delayer is active, - // so it is only enabled when needed. - super.on_enable(); - - sha1 = sha1.toLowerCase(); - if (!url.isEmpty()) { - // Check if the server has a manually configured resource pack. - // This would conflict. - Nms.server_handle().settings.getProperties().serverResourcePackInfo.ifPresent(rp_info -> { - if (!rp_info.url().trim().isEmpty()) { - get_module().log.warning("You have manually configured a resource pack in your server.properties. This cannot be used together with vane, as servers only allow serving a single resource pack."); - } - }); - - get_module().log.info("Distributing resource pack from '" + url + "' with sha1 " + sha1); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_join(final PlayerJoinEvent event) { - if (url.isEmpty()) { - return; - } - send_resource_pack(event.getPlayer()); - } - - public void send_resource_pack(Player player) { - var url2 = url; - if (localDev) { - url2 = url + "?" + counter; - player.sendMessage(url2 + " " + sha1); - } - - try { - ResourcePackInfo info = ResourcePackInfo.resourcePackInfo(uuid, new URI(url2), sha1); - player.sendResourcePacks(ResourcePackRequest.resourcePackRequest().packs(info).asResourcePackRequest()); - } catch (URISyntaxException e) { - get_module().log.warning("The provided resource pack URL is incorrect: " + url2); - }; - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) - public void on_player_status(final PlayerResourcePackStatusEvent event) { - if (!config_force || event.getPlayer().hasPermission(bypass_permission)) { - return; - } - - switch (event.getStatus()) { - case DECLINED: - event.getPlayer().kick(lang_declined.str_component()); - break; - case FAILED_DOWNLOAD: - event.getPlayer().kick(lang_download_failed.str_component()); - break; - default: - break; - } - } - - @SuppressWarnings({"deprecation", "UnstableApiUsage"}) - public void update_sha1(File file) { - if (!localDev) return; - try { - var hash = Files.asByteSource(file).hash(Hashing.sha1()); - ResourcePackDistributor.this.sha1 = hash.toString(); - } catch (IOException ignored) {} - } + // Assume debug environment if both add-plugin and vane-debug are defined, until run-paper adds + // a better way. + // https://github.com/jpenilla/run-paper/issues/14 + private static final boolean localDev = + Nms.server_handle().options.hasArgument("add-plugin") && Boolean.getBoolean("disable.watchdog"); + + @ConfigBoolean( + def = true, + desc = "Kick players if they deny to use the specified resource pack (if set). Individual players can be exempt from this rule by giving them the permission 'vane.core.resource_pack.bypass'." + ) + public boolean config_force; + + @LangMessage + public TranslatedMessage lang_declined; + + @LangMessage + public TranslatedMessage lang_download_failed; + + public String url = null; + public String sha1 = null; + public UUID uuid = null; + public int counter = 0; + + // The permission to bypass the resource pack + public final Permission bypass_permission; + + public CustomResourcePackConfig custom_resource_pack_config; + public PlayerMessageDelayer player_message_delayer; + private ResourcePackFileWatcher file_watcher; + private ResourcePackDevServer dev_server; + + public ResourcePackDistributor(Context context) { + super(context.group("resource_pack", "Enable resource pack distribution.")); + // Delay messages if this the distributor is active. + custom_resource_pack_config = new CustomResourcePackConfig(get_context()); + // Delay messages if this the distributor is active. + player_message_delayer = new PlayerMessageDelayer(get_context()); + + // Register bypass permission + bypass_permission = new Permission( + "vane." + get_module().get_name() + ".resource_pack.bypass", + "Allows bypassing an enforced resource pack", + PermissionDefault.FALSE + ); + get_module().register_permission(bypass_permission); + } + + @Override + public void on_enable() { + if (localDev) { + try { + File pack_output = new File("vane-resource-pack.zip"); + if (!pack_output.exists()) { + get_module().log.info("Resource Pack Missing, first run? Generating resource pack."); + pack_output = get_module().generate_resource_pack(); + } + file_watcher = new ResourcePackFileWatcher(this, pack_output); + dev_server = new ResourcePackDevServer(this, pack_output); + dev_server.serve(); + file_watcher.watch_for_changes(); + } catch (IOException | InterruptedException ignored) { + ignored.printStackTrace(); + } + + get_module().log.info("Setting up dev lazy server"); + } else if (((ModuleGroup) custom_resource_pack_config.get_context()).config_enabled) { + get_module().log.info("Serving custom resource pack"); + url = custom_resource_pack_config.config_url; + sha1 = custom_resource_pack_config.config_sha1; + uuid = UUID.fromString(custom_resource_pack_config.config_uuid); + } else { + get_module().log.info("Serving official vane resource pack"); + try { + Properties properties = new Properties(); + properties.load(Core.class.getResourceAsStream("/vane-core.properties")); + url = properties.getProperty("resource_pack_url"); + sha1 = properties.getProperty("resource_pack_sha1"); + uuid = UUID.fromString(properties.getProperty("resource_pack_uuid")); + } catch (IOException e) { + get_module().log.severe("Could not load official resource pack sha1 from included properties file"); + url = ""; + sha1 = ""; + uuid = UUID.randomUUID(); + } + } + + // Check sha1 sum validity + if (sha1.length() != 40) { + get_module() + .log.warning( + "Invalid resource pack SHA-1 sum '" + + sha1 + + "', should be 40 characters long but has " + + sha1.length() + + " characters" + ); + get_module().log.warning("Disabling resource pack serving and message delaying"); + + // Disable resource pack + url = ""; + // Prevent subcontexts from being enabling + // FIXME this can be coded more cleanly. We need a way + // to process config changes _before_ the module is enabled. + // like on_config_change_pre_enable(), where we can override + // the context group enable state. + ((ModuleGroup) player_message_delayer.get_context()).config_enabled = false; + } + + // Propagate enable after determining whether the player message delayer is active, + // so it is only enabled when needed. + super.on_enable(); + + sha1 = sha1.toLowerCase(); + if (!url.isEmpty()) { + // Check if the server has a manually configured resource pack. + // This would conflict. + Nms.server_handle() + .settings.getProperties() + .serverResourcePackInfo.ifPresent(rp_info -> { + if (!rp_info.url().trim().isEmpty()) { + get_module() + .log.warning( + "You have manually configured a resource pack in your server.properties. This cannot be used together with vane, as servers only allow serving a single resource pack." + ); + } + }); + + get_module().log.info("Distributing resource pack from '" + url + "' with sha1 " + sha1); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_join(final PlayerJoinEvent event) { + if (url.isEmpty()) { + return; + } + send_resource_pack(event.getPlayer()); + } + + public void send_resource_pack(Player player) { + var url2 = url; + if (localDev) { + url2 = url + "?" + counter; + player.sendMessage(url2 + " " + sha1); + } + + try { + ResourcePackInfo info = ResourcePackInfo.resourcePackInfo(uuid, new URI(url2), sha1); + player.sendResourcePacks(ResourcePackRequest.resourcePackRequest().packs(info).asResourcePackRequest()); + } catch (URISyntaxException e) { + get_module().log.warning("The provided resource pack URL is incorrect: " + url2); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) + public void on_player_status(final PlayerResourcePackStatusEvent event) { + if (!config_force || event.getPlayer().hasPermission(bypass_permission)) { + return; + } + + switch (event.getStatus()) { + case DECLINED: + event.getPlayer().kick(lang_declined.str_component()); + break; + case FAILED_DOWNLOAD: + event.getPlayer().kick(lang_download_failed.str_component()); + break; + default: + break; + } + } + + @SuppressWarnings({ "deprecation", "UnstableApiUsage" }) + public void update_sha1(File file) { + if (!localDev) return; + try { + var hash = Files.asByteSource(file).hash(Hashing.sha1()); + ResourcePackDistributor.this.sha1 = hash.toString(); + } catch (IOException ignored) {} + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackFileWatcher.java b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackFileWatcher.java index 898d80613..6bc594bb6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackFileWatcher.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackFileWatcher.java @@ -13,111 +13,111 @@ public class ResourcePackFileWatcher { - private final ResourcePackDistributor resource_pack_distributor; - private final File file; - - public ResourcePackFileWatcher(ResourcePackDistributor resource_pack_distributor, File file) - throws IOException, InterruptedException { - this.resource_pack_distributor = resource_pack_distributor; - this.file = file; - } - - public void watch_for_changes() throws IOException { - var eyes = FileSystems.getDefault().newWatchService(); - var lang_file_match = FileSystems.getDefault().getPathMatcher("glob:**/lang-*.yml"); - register_directories(Paths.get("plugins"), eyes, this::is_vane_module_folder); - - watch_async(eyes, lang_file_match, this::update_and_send_resource_pack) - .runTaskAsynchronously(resource_pack_distributor.get_module()); - } - - private void update_and_send_resource_pack() { - resource_pack_distributor.counter++; - resource_pack_distributor.get_module().generate_resource_pack(); - resource_pack_distributor.update_sha1(file); - for (Player player : Bukkit.getOnlinePlayers()) { - resource_pack_distributor.send_resource_pack(player); - } - } - - private boolean is_vane_module_folder(Path p) { - return p.getFileName().toString().startsWith("vane-"); - } - - private static class TrackRunned extends BukkitRunnable { - - final Runnable r; - boolean has_run = false; - boolean has_started = false; - - public TrackRunned(Runnable r) { - this.r = r; - } - - @Override - public void run() { - has_started = true; - r.run(); - has_run = true; - } - } - - private @NotNull BukkitRunnable watch_async(WatchService eyes, PathMatcher match_lang, Runnable on_hit) { - return new BukkitRunnable() { - @Override - public void run() { - boolean should_schedule = false; - TrackRunned runner = null; - for (;;) { - final WatchKey key; - try { - key = eyes.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - // process events - for (WatchEvent event : key.pollEvents()) { - if (event.kind() == OVERFLOW) continue; - - // This generic is always Path for WatchEvent kinds other than OVERFLOW - @SuppressWarnings("unchecked") - WatchEvent ev = (WatchEvent) event; - Path dir = (Path) key.watchable(); - Path filename = ev.context(); - if (!match_lang.matches(dir.resolve(filename))) continue; - should_schedule = true; - } - - if (should_schedule) { - if (runner != null) { - if (!runner.has_started) runner.cancel(); - } - runner = new TrackRunned(on_hit); - runner.runTaskLater(resource_pack_distributor.get_module().core, 20L); - should_schedule = false; - } - - // reset the key - boolean valid = key.reset(); - if (!valid) { - return; - } - } - } - }; - } - - private void register_directories(Path root, WatchService watcher, Predicate path_match) throws IOException { - // register vane sub-folders. - final Iterator interesting_paths = Files - .walk(root) - .filter(Files::isDirectory) - .filter(path_match) - .iterator(); - // quirky, but checked exceptions inside streams suck. - while (interesting_paths.hasNext()) { - Path p = interesting_paths.next(); - p.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); - } - } + private final ResourcePackDistributor resource_pack_distributor; + private final File file; + + public ResourcePackFileWatcher(ResourcePackDistributor resource_pack_distributor, File file) + throws IOException, InterruptedException { + this.resource_pack_distributor = resource_pack_distributor; + this.file = file; + } + + public void watch_for_changes() throws IOException { + var eyes = FileSystems.getDefault().newWatchService(); + var lang_file_match = FileSystems.getDefault().getPathMatcher("glob:**/lang-*.yml"); + register_directories(Paths.get("plugins"), eyes, this::is_vane_module_folder); + + watch_async(eyes, lang_file_match, this::update_and_send_resource_pack).runTaskAsynchronously( + resource_pack_distributor.get_module() + ); + } + + private void update_and_send_resource_pack() { + resource_pack_distributor.counter++; + resource_pack_distributor.get_module().generate_resource_pack(); + resource_pack_distributor.update_sha1(file); + for (Player player : Bukkit.getOnlinePlayers()) { + resource_pack_distributor.send_resource_pack(player); + } + } + + private boolean is_vane_module_folder(Path p) { + return p.getFileName().toString().startsWith("vane-"); + } + + private static class TrackRunned extends BukkitRunnable { + + final Runnable r; + boolean has_run = false; + boolean has_started = false; + + public TrackRunned(Runnable r) { + this.r = r; + } + + @Override + public void run() { + has_started = true; + r.run(); + has_run = true; + } + } + + private @NotNull BukkitRunnable watch_async(WatchService eyes, PathMatcher match_lang, Runnable on_hit) { + return new BukkitRunnable() { + @Override + public void run() { + boolean should_schedule = false; + TrackRunned runner = null; + for (;;) { + final WatchKey key; + try { + key = eyes.take(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + // process events + for (WatchEvent event : key.pollEvents()) { + if (event.kind() == OVERFLOW) continue; + + // This generic is always Path for WatchEvent kinds other than OVERFLOW + @SuppressWarnings("unchecked") + WatchEvent ev = (WatchEvent) event; + Path dir = (Path) key.watchable(); + Path filename = ev.context(); + if (!match_lang.matches(dir.resolve(filename))) continue; + should_schedule = true; + } + + if (should_schedule) { + if (runner != null) { + if (!runner.has_started) runner.cancel(); + } + runner = new TrackRunned(on_hit); + runner.runTaskLater(resource_pack_distributor.get_module().core, 20L); + should_schedule = false; + } + + // reset the key + boolean valid = key.reset(); + if (!valid) { + return; + } + } + } + }; + } + + private void register_directories(Path root, WatchService watcher, Predicate path_match) throws IOException { + // register vane sub-folders. + final Iterator interesting_paths = Files.walk(root) + .filter(Files::isDirectory) + .filter(path_match) + .iterator(); + // quirky, but checked exceptions inside streams suck. + while (interesting_paths.hasNext()) { + Path p = interesting_paths.next(); + p.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackGenerator.java b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackGenerator.java index 21e615b56..7db5895bd 100644 --- a/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackGenerator.java +++ b/vane-core/src/main/java/org/oddlama/vane/core/resourcepack/ResourcePackGenerator.java @@ -14,232 +14,240 @@ import java.util.function.Consumer; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; - +import net.kyori.adventure.key.Key; import org.bukkit.NamespacedKey; import org.json.JSONArray; import org.json.JSONObject; -import net.kyori.adventure.key.Key; - public class ResourcePackGenerator { - private String description = ""; - private byte[] icon_png_content = null; - private Map> translations = new HashMap<>(); - private Map> item_overrides = new HashMap<>(); - private Map item_textures = new HashMap<>(); - - public void set_description(String description) { - this.description = description; - } - - public void set_icon_png(File file) throws IOException { - this.icon_png_content = Files.readAllBytes(file.toPath()); - } - - public void set_icon_png(InputStream data) throws IOException { - this.icon_png_content = data.readAllBytes(); - } - - public JSONObject translations(String namespace, String lang_code) { - var ns = translations.computeIfAbsent(namespace, k -> new HashMap<>()); - var lang_map = ns.get(lang_code); - if (lang_map == null) { - lang_map = new JSONObject(); - ns.put(lang_code, lang_map); - } - return lang_map; - } - - public void add_item_model(NamespacedKey key, InputStream texture_png, Key parent) throws IOException { - item_textures.put(key, new PackEntry(texture_png.readAllBytes(), parent)); - } - - public void add_item_model(NamespacedKey key, InputStream texture_png, InputStream texture_png_mcmeta, Key parent) throws IOException { - item_textures.put(key, new PackEntry(texture_png.readAllBytes(), texture_png_mcmeta.readAllBytes(), parent)); - } - - public void add_item_override( - NamespacedKey base_item_key, - NamespacedKey new_item_key, - Consumer create_predicate - ) { - var overrides = item_overrides.computeIfAbsent(base_item_key, k -> new ArrayList<>()); - - final var predicate = new JSONObject(); - create_predicate.accept(predicate); - - final var override = new JSONObject(); - override.put("predicate", predicate); - override.put("model", new_item_key.getNamespace() + ":item/" + new_item_key.getKey()); - overrides.add(override); - } - - private String generate_pack_mcmeta() { - final var pack = new JSONObject(); - pack.put("pack_format", 34); - pack.put("description", description); - - final var root = new JSONObject(); - root.put("pack", pack); - - return root.toString(); - } - - private void write_translations(final ZipOutputStream zip) throws IOException { - for (var t : translations.entrySet()) { - var namespace = t.getKey(); - for (var ns : t.getValue().entrySet()) { - var lang_code = ns.getKey(); - var lang_map = ns.getValue(); - zip.putNextEntry(new ZipEntry("assets/" + namespace + "/lang/" + lang_code + ".json")); - zip.write(lang_map.toString().getBytes(StandardCharsets.UTF_8)); - zip.closeEntry(); - } - } - } - - private JSONObject create_item_model(NamespacedKey texture, Key item_type) { - // Create model json - final var model = new JSONObject(); - - // FIXME: hardcoded fixes. better rewrite RP generator - // and use static files for all items. just language should be generated. - final var textures = new JSONObject(); - if (texture.getNamespace().equals("minecraft") && texture.getKey().endsWith("shulker_box")) { - model.put("parent", "minecraft:item/template_shulker_box"); - - textures.put("particle", "minecraft:block/" + texture.getKey()); - model.put("textures", textures); - } else { - model.put("parent", item_type.toString()); - if (texture.getNamespace().equals("minecraft") && texture.getKey().equals("compass")) { - textures.put("layer0", texture.getNamespace() + ":item/compass_16"); - } else { - textures.put("layer0", texture.getNamespace() + ":item/" + texture.getKey()); - } - model.put("textures", textures); - } - - return model; - } - - private void write_item_models(final ZipOutputStream zip) throws IOException { - for (var entry : item_textures.entrySet()) { - final var key = entry.getKey(); - final var texture_png = entry.getValue().texture_png; - final var texture_png_mcmeta = entry.getValue().texture_png_mcmeta; - - // Write texture - zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/textures/item/" + key.getKey() + ".png")); - zip.write(texture_png); - zip.closeEntry(); - - // Write mcmeta if given - if (texture_png_mcmeta.length > 0) { - zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/textures/item/" + key.getKey() + ".png.mcmeta")); - zip.write(texture_png_mcmeta); - zip.closeEntry(); - } - - // Write model json - final var model = create_item_model(key, entry.getValue().parent); - zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/models/item/" + key.getKey() + ".json")); - zip.write(model.toString().getBytes(StandardCharsets.UTF_8)); - zip.closeEntry(); - } - } - - private void write_item_overrides(final ZipOutputStream zip) throws IOException { - for (var entry : item_overrides.entrySet()) { - final var key = entry.getKey(); - - final var overrides = new JSONArray(); - // Be sure to iterate in sorted order, as predicates must - // be sorted in the final json. - // Otherwise, minecraft will - // select the wrong items. - entry.getValue().stream().sorted(Comparator.comparing(o -> { - if (!o.has("predicate")) { - return 0; - } - final var pred = o.getJSONObject("predicate"); - if (!pred.has("custom_model_data")) { - return 0; - } - return pred.getInt("custom_model_data"); - })).forEach(overrides::put); - - // Create model json - final var model = create_item_model(key, override_parent(key)); - model.put("overrides", overrides); - - // Write item model override - zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/models/item/" + key.getKey() + ".json")); - zip.write(model.toString().getBytes(StandardCharsets.UTF_8)); - zip.closeEntry(); - } - } - - public void write(File file) throws IOException { - try (var zip = new ZipOutputStream(new FileOutputStream(file))) { - zip.putNextEntry(new ZipEntry("pack.mcmeta")); - zip.write(generate_pack_mcmeta().getBytes(StandardCharsets.UTF_8)); - zip.closeEntry(); - - if (icon_png_content != null) { - zip.putNextEntry(new ZipEntry("pack.png")); - zip.write(icon_png_content); - zip.closeEntry(); - } - - write_translations(zip); - write_item_models(zip); - write_item_overrides(zip); - } catch (IOException e) { - throw e; - } - } - - class PackEntry { - final byte[] texture_png; - final byte[] texture_png_mcmeta; - final Key parent; - - PackEntry(byte[] texture_png, byte[] texture_png_mcmeta, Key parent) { - this.texture_png = texture_png; - this.texture_png_mcmeta = texture_png_mcmeta; - this.parent = parent; - } - - PackEntry(byte[] texture_png, Key parent) { - this.texture_png = texture_png; - this.texture_png_mcmeta = new byte[0]; - this.parent = parent; - } - } - - /** - * Gives the type of parent used by the given item - * - * @param item_key - * @return a key containing the parent type - */ - private Key override_parent(NamespacedKey item_key){ - switch(item_key.getKey()){ - case "wooden_hoe": - case "stone_hoe": - case "iron_hoe": - case "golden_hoe": - case "diamond_hoe": - case "netherite_hoe": - return Key.key("minecraft:item/handheld"); - case "warped_fungus_on_a_stick": - return Key.key("minecraft:item/handheld_rod"); - case "dropper": - return Key.key("minecraft:block/dropper"); - default: - return Key.key("minecraft:item/generated"); - } - } + private String description = ""; + private byte[] icon_png_content = null; + private Map> translations = new HashMap<>(); + private Map> item_overrides = new HashMap<>(); + private Map item_textures = new HashMap<>(); + + public void set_description(String description) { + this.description = description; + } + + public void set_icon_png(File file) throws IOException { + this.icon_png_content = Files.readAllBytes(file.toPath()); + } + + public void set_icon_png(InputStream data) throws IOException { + this.icon_png_content = data.readAllBytes(); + } + + public JSONObject translations(String namespace, String lang_code) { + var ns = translations.computeIfAbsent(namespace, k -> new HashMap<>()); + var lang_map = ns.get(lang_code); + if (lang_map == null) { + lang_map = new JSONObject(); + ns.put(lang_code, lang_map); + } + return lang_map; + } + + public void add_item_model(NamespacedKey key, InputStream texture_png, Key parent) throws IOException { + item_textures.put(key, new PackEntry(texture_png.readAllBytes(), parent)); + } + + public void add_item_model(NamespacedKey key, InputStream texture_png, InputStream texture_png_mcmeta, Key parent) + throws IOException { + item_textures.put(key, new PackEntry(texture_png.readAllBytes(), texture_png_mcmeta.readAllBytes(), parent)); + } + + public void add_item_override( + NamespacedKey base_item_key, + NamespacedKey new_item_key, + Consumer create_predicate + ) { + var overrides = item_overrides.computeIfAbsent(base_item_key, k -> new ArrayList<>()); + + final var predicate = new JSONObject(); + create_predicate.accept(predicate); + + final var override = new JSONObject(); + override.put("predicate", predicate); + override.put("model", new_item_key.getNamespace() + ":item/" + new_item_key.getKey()); + overrides.add(override); + } + + private String generate_pack_mcmeta() { + final var pack = new JSONObject(); + pack.put("pack_format", 34); + pack.put("description", description); + + final var root = new JSONObject(); + root.put("pack", pack); + + return root.toString(); + } + + private void write_translations(final ZipOutputStream zip) throws IOException { + for (var t : translations.entrySet()) { + var namespace = t.getKey(); + for (var ns : t.getValue().entrySet()) { + var lang_code = ns.getKey(); + var lang_map = ns.getValue(); + zip.putNextEntry(new ZipEntry("assets/" + namespace + "/lang/" + lang_code + ".json")); + zip.write(lang_map.toString().getBytes(StandardCharsets.UTF_8)); + zip.closeEntry(); + } + } + } + + private JSONObject create_item_model(NamespacedKey texture, Key item_type) { + // Create model json + final var model = new JSONObject(); + + // FIXME: hardcoded fixes. better rewrite RP generator + // and use static files for all items. just language should be generated. + final var textures = new JSONObject(); + if (texture.getNamespace().equals("minecraft") && texture.getKey().endsWith("shulker_box")) { + model.put("parent", "minecraft:item/template_shulker_box"); + + textures.put("particle", "minecraft:block/" + texture.getKey()); + model.put("textures", textures); + } else { + model.put("parent", item_type.toString()); + if (texture.getNamespace().equals("minecraft") && texture.getKey().equals("compass")) { + textures.put("layer0", texture.getNamespace() + ":item/compass_16"); + } else { + textures.put("layer0", texture.getNamespace() + ":item/" + texture.getKey()); + } + model.put("textures", textures); + } + + return model; + } + + private void write_item_models(final ZipOutputStream zip) throws IOException { + for (var entry : item_textures.entrySet()) { + final var key = entry.getKey(); + final var texture_png = entry.getValue().texture_png; + final var texture_png_mcmeta = entry.getValue().texture_png_mcmeta; + + // Write texture + zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/textures/item/" + key.getKey() + ".png")); + zip.write(texture_png); + zip.closeEntry(); + + // Write mcmeta if given + if (texture_png_mcmeta.length > 0) { + zip.putNextEntry( + new ZipEntry("assets/" + key.getNamespace() + "/textures/item/" + key.getKey() + ".png.mcmeta") + ); + zip.write(texture_png_mcmeta); + zip.closeEntry(); + } + + // Write model json + final var model = create_item_model(key, entry.getValue().parent); + zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/models/item/" + key.getKey() + ".json")); + zip.write(model.toString().getBytes(StandardCharsets.UTF_8)); + zip.closeEntry(); + } + } + + private void write_item_overrides(final ZipOutputStream zip) throws IOException { + for (var entry : item_overrides.entrySet()) { + final var key = entry.getKey(); + + final var overrides = new JSONArray(); + // Be sure to iterate in sorted order, as predicates must + // be sorted in the final json. + // Otherwise, minecraft will + // select the wrong items. + entry + .getValue() + .stream() + .sorted( + Comparator.comparing(o -> { + if (!o.has("predicate")) { + return 0; + } + final var pred = o.getJSONObject("predicate"); + if (!pred.has("custom_model_data")) { + return 0; + } + return pred.getInt("custom_model_data"); + }) + ) + .forEach(overrides::put); + + // Create model json + final var model = create_item_model(key, override_parent(key)); + model.put("overrides", overrides); + + // Write item model override + zip.putNextEntry(new ZipEntry("assets/" + key.getNamespace() + "/models/item/" + key.getKey() + ".json")); + zip.write(model.toString().getBytes(StandardCharsets.UTF_8)); + zip.closeEntry(); + } + } + + public void write(File file) throws IOException { + try (var zip = new ZipOutputStream(new FileOutputStream(file))) { + zip.putNextEntry(new ZipEntry("pack.mcmeta")); + zip.write(generate_pack_mcmeta().getBytes(StandardCharsets.UTF_8)); + zip.closeEntry(); + + if (icon_png_content != null) { + zip.putNextEntry(new ZipEntry("pack.png")); + zip.write(icon_png_content); + zip.closeEntry(); + } + + write_translations(zip); + write_item_models(zip); + write_item_overrides(zip); + } catch (IOException e) { + throw e; + } + } + + class PackEntry { + + final byte[] texture_png; + final byte[] texture_png_mcmeta; + final Key parent; + + PackEntry(byte[] texture_png, byte[] texture_png_mcmeta, Key parent) { + this.texture_png = texture_png; + this.texture_png_mcmeta = texture_png_mcmeta; + this.parent = parent; + } + + PackEntry(byte[] texture_png, Key parent) { + this.texture_png = texture_png; + this.texture_png_mcmeta = new byte[0]; + this.parent = parent; + } + } + + /** + * Gives the type of parent used by the given item + * + * @param item_key + * @return a key containing the parent type + */ + private Key override_parent(NamespacedKey item_key) { + switch (item_key.getKey()) { + case "wooden_hoe": + case "stone_hoe": + case "iron_hoe": + case "golden_hoe": + case "diamond_hoe": + case "netherite_hoe": + return Key.key("minecraft:item/handheld"); + case "warped_fungus_on_a_stick": + return Key.key("minecraft:item/handheld_rod"); + case "dropper": + return Key.key("minecraft:block/dropper"); + default: + return Key.key("minecraft:item/generated"); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/packet/AbstractPacket.java b/vane-core/src/main/java/org/oddlama/vane/packet/AbstractPacket.java index 5ffe0fb2e..74877547a 100644 --- a/vane-core/src/main/java/org/oddlama/vane/packet/AbstractPacket.java +++ b/vane-core/src/main/java/org/oddlama/vane/packet/AbstractPacket.java @@ -1,20 +1,17 @@ /** - * PacketWrapper - ProtocolLib wrappers for Minecraft packets - * Copyright (C) dmulloy2 - * Copyright (C) Kristian S. Strangeland + * PacketWrapper - ProtocolLib wrappers for Minecraft packets Copyright (C) dmulloy2 + * Copyright (C) Kristian S. Strangeland * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + *

This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

You should have received a copy of the GNU General Public License along with this program. If + * not, see . */ package org.oddlama.vane.packet; @@ -26,48 +23,46 @@ public abstract class AbstractPacket { - // The packet we will be modifying - protected PacketContainer handle; + // The packet we will be modifying + protected PacketContainer handle; - /** - * Constructs a new strongly typed wrapper for the given packet. - * - * @param handle - handle to the raw packet data. - * @param type - the packet type. - */ - protected AbstractPacket(PacketContainer handle, PacketType type) { - // Make sure we're given a valid packet - if (handle == null) throw new IllegalArgumentException("Packet handle cannot be NULL."); - if (!Objects.equal(handle.getType(), type)) throw new IllegalArgumentException( - handle.getHandle() + " is not a packet of type " + type - ); + /** + * Constructs a new strongly typed wrapper for the given packet. + * + * @param handle - handle to the raw packet data. + * @param type - the packet type. + */ + protected AbstractPacket(PacketContainer handle, PacketType type) { + // Make sure we're given a valid packet + if (handle == null) throw new IllegalArgumentException("Packet handle cannot be NULL."); + if (!Objects.equal(handle.getType(), type)) throw new IllegalArgumentException( + handle.getHandle() + " is not a packet of type " + type + ); - this.handle = handle; - } + this.handle = handle; + } - /** - * Retrieve a handle to the raw packet data. - * - * @return Raw packet data. - */ - public PacketContainer getHandle() { - return handle; - } + /** + * Retrieve a handle to the raw packet data. + * + * @return Raw packet data. + */ + public PacketContainer getHandle() { + return handle; + } - /** - * Send the current packet to the given receiver. - * - * @param receiver - the receiver. - * @throws RuntimeException If the packet cannot be sent. - */ - public void sendPacket(Player receiver) { - ProtocolLibrary.getProtocolManager().sendServerPacket(receiver, getHandle()); - } + /** + * Send the current packet to the given receiver. + * + * @param receiver - the receiver. + * @throws RuntimeException If the packet cannot be sent. + */ + public void sendPacket(Player receiver) { + ProtocolLibrary.getProtocolManager().sendServerPacket(receiver, getHandle()); + } - /** - * Send the current packet to all online players. - */ - public void broadcastPacket() { - ProtocolLibrary.getProtocolManager().broadcastServerPacket(getHandle()); - } + /** Send the current packet to all online players. */ + public void broadcastPacket() { + ProtocolLibrary.getProtocolManager().broadcastServerPacket(getHandle()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/packet/WrapperPlayServerChat.java b/vane-core/src/main/java/org/oddlama/vane/packet/WrapperPlayServerChat.java index 66a5d96fb..806853bc3 100644 --- a/vane-core/src/main/java/org/oddlama/vane/packet/WrapperPlayServerChat.java +++ b/vane-core/src/main/java/org/oddlama/vane/packet/WrapperPlayServerChat.java @@ -1,20 +1,17 @@ /** - * PacketWrapper - ProtocolLib wrappers for Minecraft packets - * Copyright (C) dmulloy2 - * Copyright (C) Kristian S. Strangeland + * PacketWrapper - ProtocolLib wrappers for Minecraft packets Copyright (C) dmulloy2 + * Copyright (C) Kristian S. Strangeland * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + *

This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

You should have received a copy of the GNU General Public License along with this program. If + * not, see . */ package org.oddlama.vane.packet; @@ -27,80 +24,78 @@ public class WrapperPlayServerChat extends AbstractPacket { - public static final PacketType TYPE = PacketType.Play.Server.CHAT; + public static final PacketType TYPE = PacketType.Play.Server.CHAT; - public WrapperPlayServerChat() { - super(new PacketContainer(TYPE), TYPE); - handle.getModifier().writeDefaults(); - } + public WrapperPlayServerChat() { + super(new PacketContainer(TYPE), TYPE); + handle.getModifier().writeDefaults(); + } - public WrapperPlayServerChat(PacketContainer packet) { - super(packet, TYPE); - } + public WrapperPlayServerChat(PacketContainer packet) { + super(packet, TYPE); + } - /** - * Retrieve the chat message. - *

- * Limited to 32.767 bytes - * - * @return The current message - */ - public WrappedChatComponent getMessage() { - return handle.getChatComponents().read(0); - } + /** + * Retrieve the chat message. + * + *

Limited to 32.767 bytes + * + * @return The current message + */ + public WrappedChatComponent getMessage() { + return handle.getChatComponents().read(0); + } - /** - * Set the message. - * - * @param value - new value. - */ - public void setMessage(WrappedChatComponent value) { - handle.getChatComponents().write(0, value); - } + /** + * Set the message. + * + * @param value - new value. + */ + public void setMessage(WrappedChatComponent value) { + handle.getChatComponents().write(0, value); + } - public ChatType getChatType() { - return handle.getChatTypes().read(0); - } + public ChatType getChatType() { + return handle.getChatTypes().read(0); + } - public void setChatType(ChatType type) { - handle.getChatTypes().write(0, type); - } + public void setChatType(ChatType type) { + handle.getChatTypes().write(0, type); + } - /** - * Retrieve Position. - *

- * Notes: 0 - Chat (chat box), 1 - System Message (chat box), 2 - Above - * action bar - * - * @return The current Position - * @deprecated Magic values replaced by enum - */ - @Deprecated - public byte getPosition() { - Byte position = handle.getBytes().readSafely(0); - if (position != null) { - return position; - } else { - return getChatType().getId(); - } - } + /** + * Retrieve Position. + * + *

Notes: 0 - Chat (chat box), 1 - System Message (chat box), 2 - Above action bar + * + * @return The current Position + * @deprecated Magic values replaced by enum + */ + @Deprecated + public byte getPosition() { + Byte position = handle.getBytes().readSafely(0); + if (position != null) { + return position; + } else { + return getChatType().getId(); + } + } - /** - * Set Position. - * - * @param value - new value. - * @deprecated Magic values replaced by enum - */ - @Deprecated - public void setPosition(byte value) { - handle.getBytes().writeSafely(0, value); + /** + * Set Position. + * + * @param value - new value. + * @deprecated Magic values replaced by enum + */ + @Deprecated + public void setPosition(byte value) { + handle.getBytes().writeSafely(0, value); - if (EnumWrappers.getChatTypeClass() != null) { - Arrays - .stream(ChatType.values()) - .filter(t -> t.getId() == value) - .findAny() - .ifPresent(t -> handle.getChatTypes().writeSafely(0, t)); - } - } + if (EnumWrappers.getChatTypeClass() != null) { + Arrays.stream(ChatType.values()) + .filter(t -> t.getId() == value) + .findAny() + .ifPresent(t -> handle.getChatTypes().writeSafely(0, t)); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/ArrayUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/ArrayUtil.java index c980b08fe..2e39c7f7a 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/ArrayUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/ArrayUtil.java @@ -3,20 +3,21 @@ import java.util.Arrays; public class ArrayUtil { - public static T[] prepend(T[] arr, T element) { - final var n = arr.length; - arr = Arrays.copyOf(arr, n + 1); - for (int i = arr.length - 1; i > 0; --i) { - arr[i] = arr[i - 1]; - } - arr[0] = element; - return arr; - } - public static T[] append(T[] arr, T element) { - final var n = arr.length; - arr = Arrays.copyOf(arr, n + 1); - arr[n] = element; - return arr; - } + public static T[] prepend(T[] arr, T element) { + final var n = arr.length; + arr = Arrays.copyOf(arr, n + 1); + for (int i = arr.length - 1; i > 0; --i) { + arr[i] = arr[i - 1]; + } + arr[0] = element; + return arr; + } + + public static T[] append(T[] arr, T element) { + final var n = arr.length; + arr = Arrays.copyOf(arr, n + 1); + arr[n] = element; + return arr; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/BlockUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/BlockUtil.java index 271d38440..1a9b47039 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/BlockUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/BlockUtil.java @@ -8,373 +8,365 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; - +import net.minecraft.core.BlockPos; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Skull; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; -import io.netty.buffer.Unpooled; -import net.minecraft.core.BlockPos; - public class BlockUtil { - public static final List BLOCK_FACES = Arrays.asList( - BlockFace.NORTH, - BlockFace.EAST, - BlockFace.SOUTH, - BlockFace.WEST, - BlockFace.UP, - BlockFace.DOWN - ); - - public static final List XZ_FACES = Arrays.asList( - BlockFace.NORTH, - BlockFace.EAST, - BlockFace.SOUTH, - BlockFace.WEST - ); - - public static final int NEAREST_RELATIVE_BLOCKS_FOR_RADIUS_MAX = 6; - public static final List> NEAREST_RELATIVE_BLOCKS_FOR_RADIUS = new ArrayList<>(); - - static { - for (int i = 0; i <= NEAREST_RELATIVE_BLOCKS_FOR_RADIUS_MAX; ++i) { - NEAREST_RELATIVE_BLOCKS_FOR_RADIUS.add(nearest_blocks_for_radius(i)); - } - } - - public static boolean equals_pos(final Block b1, final Block b2) { - return b1.getX() == b2.getX() && b1.getY() == b2.getY() && b1.getZ() == b2.getZ(); - } - - public static void drop_naturally(Block block, ItemStack drop) { - drop_naturally(block.getLocation().add(0.5, 0.5, 0.5), drop); - } - - public static void drop_naturally(Location loc, ItemStack drop) { - loc - .getWorld() - .dropItem(loc.add(Vector.getRandom().subtract(new Vector(.5, .5, .5)).multiply(0.5)), drop) - .setVelocity(Vector.getRandom().add(new Vector(-.5, +.5, -.5)).normalize().multiply(.15)); - } - - public static List nearest_blocks_for_radius(int radius) { - final var ret = new ArrayList(); - - // Use square bounding box - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - // Only circular area - if (x * x + z * z > radius * radius + 0.5) { - continue; - } - - ret.add(new BlockVector(x, 0, z)); - } - } - - Collections.sort(ret, new BlockVectorRadiusComparator()); - return ret; - } - - public static @NotNull Block relative(@NotNull final Block block, @NotNull final Vector relative) { - return block.getRelative(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()); - } - - public static Block next_tillable_block(final Block root_block, int radius, boolean careless) { - for (final var relative_pos : NEAREST_RELATIVE_BLOCKS_FOR_RADIUS.get(radius)) { - final var block = relative(root_block, relative_pos); - - // Check for a tillable material - if (!is_tillable(block.getType())) { - continue; - } - - // Get block above - final var above = block.getRelative(0, 1, 0); - - if (above.getType() == Material.AIR) { - // If the block above is air, we can till the block. - return block; - } else if (careless && is_replaceable_grass(above.getType())) { - // If the block above is replaceable grass, delete it and return the block. - above.setType(Material.AIR); - return block; - } - } - - // We are outside the radius. - return null; - } - - public static Block[] adjacent_blocks_3d(final Block root) { - final var adjacent = new Block[26]; - - // Direct adjacent - adjacent[0] = root.getRelative(1, 0, 0); - adjacent[1] = root.getRelative(-1, 0, 0); - adjacent[2] = root.getRelative(0, 0, 1); - adjacent[3] = root.getRelative(0, 0, -1); - adjacent[4] = root.getRelative(0, 1, 0); - adjacent[5] = root.getRelative(0, -1, 0); - - // Edge adjacent - adjacent[6] = root.getRelative(0, -1, -1); - adjacent[7] = root.getRelative(0, -1, 1); - adjacent[8] = root.getRelative(0, 1, -1); - adjacent[9] = root.getRelative(0, 1, 1); - adjacent[10] = root.getRelative(-1, 0, -1); - adjacent[11] = root.getRelative(-1, 0, 1); - adjacent[12] = root.getRelative(1, 0, -1); - adjacent[13] = root.getRelative(1, 0, 1); - adjacent[14] = root.getRelative(-1, -1, 0); - adjacent[15] = root.getRelative(-1, 1, 0); - adjacent[16] = root.getRelative(1, -1, 0); - adjacent[17] = root.getRelative(1, 1, 0); - - // Corner adjacent - adjacent[18] = root.getRelative(-1, -1, -1); - adjacent[19] = root.getRelative(-1, -1, 1); - adjacent[20] = root.getRelative(-1, 1, -1); - adjacent[21] = root.getRelative(-1, 1, 1); - adjacent[22] = root.getRelative(1, -1, -1); - adjacent[23] = root.getRelative(1, -1, 1); - adjacent[24] = root.getRelative(1, 1, -1); - adjacent[25] = root.getRelative(1, 1, 1); - - return adjacent; - } - - public static Block next_seedable_block(final Block root_block, Material farmland_type, int radius) { - for (var relative_pos : NEAREST_RELATIVE_BLOCKS_FOR_RADIUS.get(radius)) { - final var block = relative(root_block, relative_pos); - final var below = block.getRelative(BlockFace.DOWN); - - // The Block below must be farmland and the block itself must be air - if (below.getType() == farmland_type && block.getType() == Material.AIR) { - return block; - } - } - - // We are outside the radius. - return null; - } - - public static void update_lever(final Block block, final BlockFace face) { - final var level = ((CraftWorld)block.getWorld()).getHandle(); - final var connected_block = block.getRelative(face.getOppositeFace()); - final var block_pos_1 = new BlockPos(block.getX(), block.getY(), block.getZ()); - final var block_pos_2 = new BlockPos(connected_block.getX(), connected_block.getY(), connected_block.getZ()); - final var initiator_block = level.getBlockIfLoaded(block_pos_1); - level.updateNeighborsAt(block_pos_1, initiator_block); - level.updateNeighborsAt(block_pos_2, initiator_block); - } - - public static class Corner { - - private boolean x; - private boolean y; - private boolean z; - private int id; - - public Corner(final Vector hit) { - this(hit.getX() >= 0.0, hit.getY() >= 0.0, hit.getZ() >= 0.0); - } - - public Corner(boolean x, boolean y, boolean z) { - this.x = x; - this.y = y; - this.z = z; - final var mx = x ? 1 : 0; - final var my = y ? 1 : 0; - final var mz = z ? 1 : 0; - this.id = (mx << 2) | (my << 1) | (mz << 0); - } - - public boolean up() { - return y; - } - - public boolean east() { - return x; - } - - public boolean south() { - return z; - } - - public Corner up(boolean up) { - return new Corner(x, up, z); - } - - public Corner east(boolean east) { - return new Corner(east, y, z); - } - - public Corner south(boolean south) { - return new Corner(x, y, south); - } - - /** - * Rotates the corner as if it were on a north facing block - * given the block's rotation. - */ - public Corner rotate_to_north_reference(final BlockFace rotation) { - switch (rotation) { - default: - throw new IllegalArgumentException("rotation must be one of NORTH, EAST, SOUTH, WEST"); - case NORTH: - return new Corner(x, y, z); - case EAST: - return new Corner(z, y, !x); - case SOUTH: - return new Corner(!x, y, !z); - case WEST: - return new Corner(!z, y, x); - } - } - - /** Returns {NORTH, SOUTH}_{EAST, WEST} to indicate the XZ corner. */ - public BlockFace xz_face() { - if (x) { - if (z) { - return BlockFace.SOUTH_EAST; - } else { - return BlockFace.NORTH_EAST; - } - } else { - if (z) { - return BlockFace.SOUTH_WEST; - } else { - return BlockFace.NORTH_WEST; - } - } - } - - @Override - public int hashCode() { - return id; - } - - @Override - public boolean equals(Object other) { - return other instanceof Corner && ((Corner) other).id == id; - } - } - - public static class Oct { - - private Vector hit_pos; // Relative to a block middle - private Corner corner; - private BlockFace face; - - public Oct(final Vector hit_pos, final BlockFace face) { - this.hit_pos = hit_pos; - this.corner = new Corner(hit_pos); - this.face = face; - } - - public Vector hit_pos() { - return hit_pos; - } - - public Corner corner() { - return corner; - } - - public BlockFace face() { - return face; - } - } - - public static Oct raytrace_oct(final LivingEntity entity, final Block block) { - // Ray-trace position and face - final var result = entity.rayTraceBlocks(10.0); - if (block == null || result == null || !block.equals(result.getHitBlock())) { - return null; - } - - // Get in-block hit position and bias the result - // a bit inside the clicked face, so we don't get ambiguous results. - final var block_middle = block.getLocation().toVector().add(new Vector(0.5, 0.5, 0.5)); - final var hit = result - .getHitPosition() - .subtract(block_middle) - .subtract(result.getHitBlockFace().getDirection().multiply(0.25)); - - return new Oct(hit, result.getHitBlockFace()); - } - - public static class RaytraceDominantFaceResult { - - public BlockFace face = null; - public double dominance = 0.0; - } - - public static RaytraceDominantFaceResult raytrace_dominant_face(final LivingEntity entity, final Block block) { - // Ray trace clicked face - final var result = entity.rayTraceBlocks(10.0); - if (block == null || result == null || !block.equals(result.getHitBlock())) { - return null; - } - - final var block_middle = block.getLocation().toVector().add(new Vector(0.5, 0.5, 0.5)); - final var hit_position = result.getHitPosition(); - final var diff = hit_position.subtract(block_middle); - - final var ret = new RaytraceDominantFaceResult(); - for (final var face : BlockUtil.XZ_FACES) { - // Calculate how dominant the current face contributes to the clicked point - final double face_dominance; - if (face.getModX() != 0) { - face_dominance = diff.getX() * face.getModX(); - } else { //if (face.getModZ() != 0) { - face_dominance = diff.getZ() * face.getModZ(); - } - - // Find maximum dominant face - if (face_dominance > ret.dominance) { - ret.face = face; - ret.dominance = face_dominance; - } - } - - return ret; - } - - public static String texture_from_skull(final Skull skull) { - final var profile = skull.getPlayerProfile(); - if (profile == null) { - return null; - } - - for (final var property : profile.getProperties()) { - if ("textures".equals(property.getName())) { - return property.getValue(); - } - } - - return null; - } - - public static class BlockVectorRadiusComparator implements Comparator { - - @Override - public int compare(BlockVector a, BlockVector b) { - return ( - (a.getBlockX() * a.getBlockX() + a.getBlockY() * a.getBlockY() + a.getBlockZ() * a.getBlockZ()) - - (b.getBlockX() * b.getBlockX() + b.getBlockY() * b.getBlockY() + b.getBlockZ() * b.getBlockZ()) - ); - } - } + public static final List BLOCK_FACES = Arrays.asList( + BlockFace.NORTH, + BlockFace.EAST, + BlockFace.SOUTH, + BlockFace.WEST, + BlockFace.UP, + BlockFace.DOWN + ); + + public static final List XZ_FACES = Arrays.asList( + BlockFace.NORTH, + BlockFace.EAST, + BlockFace.SOUTH, + BlockFace.WEST + ); + + public static final int NEAREST_RELATIVE_BLOCKS_FOR_RADIUS_MAX = 6; + public static final List> NEAREST_RELATIVE_BLOCKS_FOR_RADIUS = new ArrayList<>(); + + static { + for (int i = 0; i <= NEAREST_RELATIVE_BLOCKS_FOR_RADIUS_MAX; ++i) { + NEAREST_RELATIVE_BLOCKS_FOR_RADIUS.add(nearest_blocks_for_radius(i)); + } + } + + public static boolean equals_pos(final Block b1, final Block b2) { + return b1.getX() == b2.getX() && b1.getY() == b2.getY() && b1.getZ() == b2.getZ(); + } + + public static void drop_naturally(Block block, ItemStack drop) { + drop_naturally(block.getLocation().add(0.5, 0.5, 0.5), drop); + } + + public static void drop_naturally(Location loc, ItemStack drop) { + loc + .getWorld() + .dropItem(loc.add(Vector.getRandom().subtract(new Vector(.5, .5, .5)).multiply(0.5)), drop) + .setVelocity(Vector.getRandom().add(new Vector(-.5, +.5, -.5)).normalize().multiply(.15)); + } + + public static List nearest_blocks_for_radius(int radius) { + final var ret = new ArrayList(); + + // Use square bounding box + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + // Only circular area + if (x * x + z * z > radius * radius + 0.5) { + continue; + } + + ret.add(new BlockVector(x, 0, z)); + } + } + + Collections.sort(ret, new BlockVectorRadiusComparator()); + return ret; + } + + public static @NotNull Block relative(@NotNull final Block block, @NotNull final Vector relative) { + return block.getRelative(relative.getBlockX(), relative.getBlockY(), relative.getBlockZ()); + } + + public static Block next_tillable_block(final Block root_block, int radius, boolean careless) { + for (final var relative_pos : NEAREST_RELATIVE_BLOCKS_FOR_RADIUS.get(radius)) { + final var block = relative(root_block, relative_pos); + + // Check for a tillable material + if (!is_tillable(block.getType())) { + continue; + } + + // Get block above + final var above = block.getRelative(0, 1, 0); + + if (above.getType() == Material.AIR) { + // If the block above is air, we can till the block. + return block; + } else if (careless && is_replaceable_grass(above.getType())) { + // If the block above is replaceable grass, delete it and return the block. + above.setType(Material.AIR); + return block; + } + } + + // We are outside the radius. + return null; + } + + public static Block[] adjacent_blocks_3d(final Block root) { + final var adjacent = new Block[26]; + + // Direct adjacent + adjacent[0] = root.getRelative(1, 0, 0); + adjacent[1] = root.getRelative(-1, 0, 0); + adjacent[2] = root.getRelative(0, 0, 1); + adjacent[3] = root.getRelative(0, 0, -1); + adjacent[4] = root.getRelative(0, 1, 0); + adjacent[5] = root.getRelative(0, -1, 0); + + // Edge adjacent + adjacent[6] = root.getRelative(0, -1, -1); + adjacent[7] = root.getRelative(0, -1, 1); + adjacent[8] = root.getRelative(0, 1, -1); + adjacent[9] = root.getRelative(0, 1, 1); + adjacent[10] = root.getRelative(-1, 0, -1); + adjacent[11] = root.getRelative(-1, 0, 1); + adjacent[12] = root.getRelative(1, 0, -1); + adjacent[13] = root.getRelative(1, 0, 1); + adjacent[14] = root.getRelative(-1, -1, 0); + adjacent[15] = root.getRelative(-1, 1, 0); + adjacent[16] = root.getRelative(1, -1, 0); + adjacent[17] = root.getRelative(1, 1, 0); + + // Corner adjacent + adjacent[18] = root.getRelative(-1, -1, -1); + adjacent[19] = root.getRelative(-1, -1, 1); + adjacent[20] = root.getRelative(-1, 1, -1); + adjacent[21] = root.getRelative(-1, 1, 1); + adjacent[22] = root.getRelative(1, -1, -1); + adjacent[23] = root.getRelative(1, -1, 1); + adjacent[24] = root.getRelative(1, 1, -1); + adjacent[25] = root.getRelative(1, 1, 1); + + return adjacent; + } + + public static Block next_seedable_block(final Block root_block, Material farmland_type, int radius) { + for (var relative_pos : NEAREST_RELATIVE_BLOCKS_FOR_RADIUS.get(radius)) { + final var block = relative(root_block, relative_pos); + final var below = block.getRelative(BlockFace.DOWN); + + // The Block below must be farmland and the block itself must be air + if (below.getType() == farmland_type && block.getType() == Material.AIR) { + return block; + } + } + + // We are outside the radius. + return null; + } + + public static void update_lever(final Block block, final BlockFace face) { + final var level = ((CraftWorld) block.getWorld()).getHandle(); + final var connected_block = block.getRelative(face.getOppositeFace()); + final var block_pos_1 = new BlockPos(block.getX(), block.getY(), block.getZ()); + final var block_pos_2 = new BlockPos(connected_block.getX(), connected_block.getY(), connected_block.getZ()); + final var initiator_block = level.getBlockIfLoaded(block_pos_1); + level.updateNeighborsAt(block_pos_1, initiator_block); + level.updateNeighborsAt(block_pos_2, initiator_block); + } + + public static class Corner { + + private boolean x; + private boolean y; + private boolean z; + private int id; + + public Corner(final Vector hit) { + this(hit.getX() >= 0.0, hit.getY() >= 0.0, hit.getZ() >= 0.0); + } + + public Corner(boolean x, boolean y, boolean z) { + this.x = x; + this.y = y; + this.z = z; + final var mx = x ? 1 : 0; + final var my = y ? 1 : 0; + final var mz = z ? 1 : 0; + this.id = (mx << 2) | (my << 1) | (mz << 0); + } + + public boolean up() { + return y; + } + + public boolean east() { + return x; + } + + public boolean south() { + return z; + } + + public Corner up(boolean up) { + return new Corner(x, up, z); + } + + public Corner east(boolean east) { + return new Corner(east, y, z); + } + + public Corner south(boolean south) { + return new Corner(x, y, south); + } + + /** Rotates the corner as if it were on a north facing block given the block's rotation. */ + public Corner rotate_to_north_reference(final BlockFace rotation) { + switch (rotation) { + default: + throw new IllegalArgumentException("rotation must be one of NORTH, EAST, SOUTH, WEST"); + case NORTH: + return new Corner(x, y, z); + case EAST: + return new Corner(z, y, !x); + case SOUTH: + return new Corner(!x, y, !z); + case WEST: + return new Corner(!z, y, x); + } + } + + /** Returns {NORTH, SOUTH}_{EAST, WEST} to indicate the XZ corner. */ + public BlockFace xz_face() { + if (x) { + if (z) { + return BlockFace.SOUTH_EAST; + } else { + return BlockFace.NORTH_EAST; + } + } else { + if (z) { + return BlockFace.SOUTH_WEST; + } else { + return BlockFace.NORTH_WEST; + } + } + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object other) { + return other instanceof Corner && ((Corner) other).id == id; + } + } + + public static class Oct { + + private Vector hit_pos; // Relative to a block middle + private Corner corner; + private BlockFace face; + + public Oct(final Vector hit_pos, final BlockFace face) { + this.hit_pos = hit_pos; + this.corner = new Corner(hit_pos); + this.face = face; + } + + public Vector hit_pos() { + return hit_pos; + } + + public Corner corner() { + return corner; + } + + public BlockFace face() { + return face; + } + } + + public static Oct raytrace_oct(final LivingEntity entity, final Block block) { + // Ray-trace position and face + final var result = entity.rayTraceBlocks(10.0); + if (block == null || result == null || !block.equals(result.getHitBlock())) { + return null; + } + + // Get in-block hit position and bias the result + // a bit inside the clicked face, so we don't get ambiguous results. + final var block_middle = block.getLocation().toVector().add(new Vector(0.5, 0.5, 0.5)); + final var hit = result + .getHitPosition() + .subtract(block_middle) + .subtract(result.getHitBlockFace().getDirection().multiply(0.25)); + + return new Oct(hit, result.getHitBlockFace()); + } + + public static class RaytraceDominantFaceResult { + + public BlockFace face = null; + public double dominance = 0.0; + } + + public static RaytraceDominantFaceResult raytrace_dominant_face(final LivingEntity entity, final Block block) { + // Ray trace clicked face + final var result = entity.rayTraceBlocks(10.0); + if (block == null || result == null || !block.equals(result.getHitBlock())) { + return null; + } + + final var block_middle = block.getLocation().toVector().add(new Vector(0.5, 0.5, 0.5)); + final var hit_position = result.getHitPosition(); + final var diff = hit_position.subtract(block_middle); + + final var ret = new RaytraceDominantFaceResult(); + for (final var face : BlockUtil.XZ_FACES) { + // Calculate how dominant the current face contributes to the clicked point + final double face_dominance; + if (face.getModX() != 0) { + face_dominance = diff.getX() * face.getModX(); + } else { // if (face.getModZ() != 0) { + face_dominance = diff.getZ() * face.getModZ(); + } + + // Find maximum dominant face + if (face_dominance > ret.dominance) { + ret.face = face; + ret.dominance = face_dominance; + } + } + + return ret; + } + + public static String texture_from_skull(final Skull skull) { + final var profile = skull.getPlayerProfile(); + if (profile == null) { + return null; + } + + for (final var property : profile.getProperties()) { + if ("textures".equals(property.getName())) { + return property.getValue(); + } + } + + return null; + } + + public static class BlockVectorRadiusComparator implements Comparator { + + @Override + public int compare(BlockVector a, BlockVector b) { + return ( + (a.getBlockX() * a.getBlockX() + a.getBlockY() * a.getBlockY() + a.getBlockZ() * a.getBlockZ()) - + (b.getBlockX() * b.getBlockX() + b.getBlockY() * b.getBlockY() + b.getBlockZ() * b.getBlockZ()) + ); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/Conversions.java b/vane-core/src/main/java/org/oddlama/vane/util/Conversions.java index d57d69ca5..3505647d1 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/Conversions.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/Conversions.java @@ -1,21 +1,22 @@ package org.oddlama.vane.util; public class Conversions { - public static long ms_to_ticks(long ms) { - return ms / 50; - } - public static long ticks_to_ms(long ticks) { - return ticks * 50; - } + public static long ms_to_ticks(long ms) { + return ms / 50; + } - public static int exp_for_level(int level) { - if (level < 17) { - return level * level + 6 * level; - } else if (level < 32) { - return (int) (2.5 * level * level - 40.5 * level) + 360; - } else { - return (int) (4.5 * level * level - 162.5 * level) + 2220; - } - } + public static long ticks_to_ms(long ticks) { + return ticks * 50; + } + + public static int exp_for_level(int level) { + if (level < 17) { + return level * level + 6 * level; + } else if (level < 32) { + return (int) (2.5 * level * level - 40.5 * level) + 360; + } else { + return (int) (4.5 * level * level - 162.5 * level) + 2220; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/IOUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/IOUtil.java index 58cdf4e23..d994b183c 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/IOUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/IOUtil.java @@ -7,24 +7,27 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; - import org.json.JSONException; import org.json.JSONObject; public class IOUtil { - private static String read_all(Reader rd) throws IOException { - final var sb = new StringBuilder(); - int cp; - while ((cp = rd.read()) != -1) { - sb.append((char) cp); - } - return sb.toString(); - } - public static JSONObject read_json_from_url(String url) throws IOException, JSONException, URISyntaxException { - try (final var rd = new BufferedReader( - new InputStreamReader(new URI(url).toURL().openStream(), StandardCharsets.UTF_8))) { - return new JSONObject(read_all(rd)); - } - } + private static String read_all(Reader rd) throws IOException { + final var sb = new StringBuilder(); + int cp; + while ((cp = rd.read()) != -1) { + sb.append((char) cp); + } + return sb.toString(); + } + + public static JSONObject read_json_from_url(String url) throws IOException, JSONException, URISyntaxException { + try ( + final var rd = new BufferedReader( + new InputStreamReader(new URI(url).toURL().openStream(), StandardCharsets.UTF_8) + ) + ) { + return new JSONObject(read_all(rd)); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java index c0a692fca..4b8bd5d40 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/ItemUtil.java @@ -5,12 +5,26 @@ import static org.oddlama.vane.util.Nms.item_handle; import static org.oddlama.vane.util.Nms.player_handle; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; - +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.item.ItemParser; +import net.minecraft.data.registries.VanillaRegistries; +import net.minecraft.world.item.Item; import org.apache.commons.lang3.tuple.Pair; import org.bukkit.Bukkit; import org.bukkit.GameMode; @@ -29,373 +43,353 @@ import org.oddlama.vane.core.Core; import org.oddlama.vane.core.material.ExtendedMaterial; -import com.destroystokyo.paper.profile.ProfileProperty; -import com.mojang.brigadier.StringReader; -import com.mojang.brigadier.exceptions.CommandSyntaxException; - -import io.papermc.paper.registry.RegistryAccess; -import io.papermc.paper.registry.RegistryKey; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import net.minecraft.commands.Commands; -import net.minecraft.commands.arguments.item.ItemParser; -import net.minecraft.data.registries.VanillaRegistries; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.item.Item; - public class ItemUtil { - private static final UUID SKULL_OWNER = UUID.fromString("00000000-0000-0000-0000-000000000000"); - - public static void damage_item(final Player player, final ItemStack item_stack, final int amount) { - if (player.getGameMode() == GameMode.CREATIVE) { // don't damage the tool if the player is in creative - return; - } - - if (amount <= 0) { - return; - } - - final var handle = item_handle(item_stack); - if (handle == null) { - return; - } - - handle.hurtAndBreak(amount, Nms.world_handle(player.getWorld()), player_handle(player), (item) -> { - player.broadcastSlotBreak(EquipmentSlot.HAND); - item_stack.subtract(); - }); - } - - public static String name_of(final ItemStack item) { - if (item == null || !item.hasItemMeta()) { - return ""; - } - final var meta = item.getItemMeta(); - if (!meta.hasDisplayName()) { - return ""; - } - - return PlainTextComponentSerializer.plainText().serialize(meta.displayName()); - } - - public static ItemStack name_item(final ItemStack item, final Component name) { - return name_item(item, name, (List) null); - } - - public static ItemStack name_item(final ItemStack item, final Component name, Component lore) { - lore = lore.decoration(TextDecoration.ITALIC, false); - return name_item(item, name, List.of(lore)); - } - - public static ItemStack set_lore(final ItemStack item, final List lore) { - item.editMeta(meta -> { - final var list = lore - .stream() - .map(x -> x.decoration(TextDecoration.ITALIC, false)) - .collect(Collectors.toList()); - meta.lore(list); - }); - - return item; - } - - public static ItemStack name_item(final ItemStack item, Component name, final List lore) { - final var meta = item.getItemMeta(); - - name = name.decoration(TextDecoration.ITALIC, false); - meta.displayName(name); - - if (lore != null) { - final var list = lore - .stream() - .map(x -> x.decoration(TextDecoration.ITALIC, false)) - .collect(Collectors.toList()); - meta.lore(list); - } - - item.setItemMeta(meta); - return item; - } - - public static int compare_enchantments(final ItemStack item_a, final ItemStack item_b) { - var ae = item_a.getEnchantments(); - var be = item_b.getEnchantments(); - - final var a_meta = item_a.getItemMeta(); - if (a_meta instanceof EnchantmentStorageMeta) { - final var stored = ((EnchantmentStorageMeta) a_meta).getStoredEnchants(); - if (stored.size() > 0) { - ae = stored; - } - } - - final var b_meta = item_b.getItemMeta(); - if (b_meta instanceof EnchantmentStorageMeta) { - final var stored = ((EnchantmentStorageMeta) b_meta).getStoredEnchants(); - if (stored.size() > 0) { - be = stored; - } - } - - // Unenchanted first - final var a_count = ae.size(); - final var b_count = be.size(); - if (a_count == 0 && b_count == 0) { - return 0; - } else if (a_count == 0) { - return -1; - } else if (b_count == 0) { - return 1; - } - - // More enchantments before fewer enchantments - if (a_count != b_count) { - return b_count - a_count; - } - - final var a_sorted = ae - .entrySet() - .stream() - .sorted( - Map.Entry - .comparingByKey( - (a, b) -> a.getKey().toString().compareTo(b.getKey().toString())) - .thenComparing(Map.Entry.comparingByValue())) - .toList(); - final var b_sorted = be - .entrySet() - .stream() - .sorted( - Map.Entry - .comparingByKey( - (a, b) -> a.getKey().toString().compareTo(b.getKey().toString())) - .thenComparing(Map.Entry.comparingByValue())) - .toList(); - - // Lastly, compare names and levels - final var ait = a_sorted.iterator(); - final var bit = b_sorted.iterator(); - - while (ait.hasNext()) { - final var a_el = ait.next(); - final var b_el = bit.next(); - - // Lexicographic name comparison - final var name_diff = a_el.getKey().getKey().toString().compareTo(b_el.getKey().getKey().toString()); - if (name_diff != 0) { - return name_diff; - } - - // Level - final int level_diff = b_el.getValue() - a_el.getValue(); - if (level_diff != 0) { - return level_diff; - } - } - - return 0; - } - - public static class ItemStackComparator implements Comparator { - - @Override - public int compare(final ItemStack a, final ItemStack b) { - if (a == null && b == null) { - return 0; - } else if (a == null) { - return 1; - } else if (b == null) { - return -1; - } - - final var na = item_handle(a); - final var nb = item_handle(b); - if (na.isEmpty()) { - return nb.isEmpty() ? 0 : 1; - } else if (nb.isEmpty()) { - return -1; - } - - // By creative mode tab - final var creative_mode_tab_diff = creative_tab_id(na) - creative_tab_id(nb); - if (creative_mode_tab_diff != 0) { - return creative_mode_tab_diff; - } - - // By id - final var id_diff = Item.getId(na.getItem()) - Item.getId(nb.getItem()); - if (id_diff != 0) { - return id_diff; - } - - // By damage - final var damage_diff = na.getDamageValue() - nb.getDamageValue(); - if (damage_diff != 0) { - return damage_diff; - } - - // By count - final var count_diff = nb.getCount() - na.getCount(); - if (count_diff != 0) { - return count_diff; - } - - // By enchantments - return compare_enchantments(a, b); - } - } - - public static ItemStack skull_for_player(final OfflinePlayer player, final boolean is_for_menu) { - final var item = new ItemStack(Material.PLAYER_HEAD); - if (!is_for_menu || Core.instance().config_player_heads_in_menus) { - item.editMeta(SkullMeta.class, meta -> meta.setOwningPlayer(player)); - } - return item; - } - - public static ItemStack skull_with_texture(final String name, final String base64_texture) { - final var profile = Bukkit.createProfileExact(SKULL_OWNER, "-"); - profile.setProperty(new ProfileProperty("textures", base64_texture)); - - final var item = new ItemStack(Material.PLAYER_HEAD); - final var meta = (SkullMeta) item.getItemMeta(); - final var name_component = Component - .text(name) - .decoration(TextDecoration.ITALIC, false) - .color(NamedTextColor.YELLOW); - meta.displayName(name_component); - meta.setPlayerProfile(profile); - item.setItemMeta(meta); - return item; - } - - /** - * Returns true if the given component is guarded by the given sentinel. - */ - public static boolean has_sentinel(final Component component, final NamespacedKey sentiel) { - if (component == null) { - return false; - } - - final var hover = component.hoverEvent(); - if (hover == null) { - return false; - } - - if (hover.value() instanceof final TextComponent hover_text) { - return hover.action() == SHOW_TEXT && sentiel.toString().equals(hover_text.content()); - } else { - return false; - } - } - - public static Component add_sentinel(final Component component, final NamespacedKey sentinel) { - return component.hoverEvent(HoverEvent.showText(Component.text(sentinel.toString()))); - } - - /** - * Applies enchantments to the item given in the form - * "{[*][,[*]]...}". - * Throws IllegalArgumentException if an enchantment cannot be found. - */ - private static ItemStack apply_enchants(final ItemStack item_stack, @Nullable String enchants) { - if (enchants == null) { - return item_stack; - } - - enchants = enchants.trim(); - if (!enchants.startsWith("{") || !enchants.endsWith("}")) { - throw new IllegalArgumentException( - "enchantments must be of form {[*][,[*]]...}"); - } - - final var parts = enchants.substring(1, enchants.length() - 1).split(","); - for (var part : parts) { - part = part.trim(); - - String key = part; - int level = 1; - final int level_delim = key.indexOf('*'); - if (level_delim != -1) { - level = Integer.parseInt(key.substring(level_delim + 1)); - key = key.substring(0, level_delim); - } - - final var ench = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT) - .get(NamespacedKey.fromString(key)); - if (ench == null) { - throw new IllegalArgumentException( - "Cannot apply unknown enchantment '" + key + "' to item '" + item_stack + "'"); - } - - if (item_stack.getType() == Material.ENCHANTED_BOOK) { - final var flevel = level; - item_stack.editMeta(EnchantmentStorageMeta.class, meta -> meta.addStoredEnchant(ench, flevel, false)); - } else { - item_stack.addEnchantment(ench, level); - } - } - - if (parts.length > 0) { - Core.instance().enchantment_manager.update_enchanted_item(item_stack); - } - return item_stack; - } - - /** - * Returns the itemstack and a boolean indicating whether it was just as simlpe - * material. - */ - public static @NotNull Pair itemstack_from_string(String definition) { - // namespace:key[[components]][#enchants{}], where the key can reference a - // material, head material or customitem. - final var enchants_delim = definition.indexOf("#enchants{"); - String enchants = null; - if (enchants_delim != -1) { - enchants = definition.substring(enchants_delim + 9); // Let it start at '{' - definition = definition.substring(0, enchants_delim); - } - - final var nbt_delim = definition.indexOf('['); - NamespacedKey key; - if (nbt_delim == -1) { - key = NamespacedKey.fromString(definition); - } else { - key = NamespacedKey.fromString(definition.substring(0, nbt_delim)); - } - - final var emat = ExtendedMaterial.from(key); - if (emat == null) { - throw new IllegalArgumentException("Invalid extended material definition: " + definition); - } - - // First, create the itemstack as if we had no NBT information. - final var item_stack = emat.item(); - - // If there is no NBT information, we can return here. - if (nbt_delim == -1) { - return Pair.of(apply_enchants(item_stack, enchants), emat.is_simple_material() && enchants == null); - } - - // Parse the NBT by using minecraft's internal parser with the base material - // of whatever the extended material gave us. - final var vanilla_definition = item_stack.getType().key() + definition.substring(nbt_delim); - try { - final var parsed_nbt = new ItemParser(Commands.createValidationContext(VanillaRegistries.createLookup())) - .parse(new StringReader(vanilla_definition)).components(); - - // Now apply the NBT be parsed by minecraft's internal parser to the itemstack. - final var nms_item = item_handle(item_stack).copy(); - nms_item.applyComponents(parsed_nbt); - - return Pair.of(apply_enchants(CraftItemStack.asCraftMirror(nms_item), enchants), false); - } catch (final CommandSyntaxException e) { - throw new IllegalArgumentException("Could not parse NBT of item definition: " + definition, e); - } - } + private static final UUID SKULL_OWNER = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + public static void damage_item(final Player player, final ItemStack item_stack, final int amount) { + if (player.getGameMode() == GameMode.CREATIVE) { // don't damage the tool if the player is in creative + return; + } + + if (amount <= 0) { + return; + } + + final var handle = item_handle(item_stack); + if (handle == null) { + return; + } + + handle.hurtAndBreak(amount, Nms.world_handle(player.getWorld()), player_handle(player), item -> { + player.broadcastSlotBreak(EquipmentSlot.HAND); + item_stack.subtract(); + }); + } + + public static String name_of(final ItemStack item) { + if (item == null || !item.hasItemMeta()) { + return ""; + } + final var meta = item.getItemMeta(); + if (!meta.hasDisplayName()) { + return ""; + } + + return PlainTextComponentSerializer.plainText().serialize(meta.displayName()); + } + + public static ItemStack name_item(final ItemStack item, final Component name) { + return name_item(item, name, (List) null); + } + + public static ItemStack name_item(final ItemStack item, final Component name, Component lore) { + lore = lore.decoration(TextDecoration.ITALIC, false); + return name_item(item, name, List.of(lore)); + } + + public static ItemStack set_lore(final ItemStack item, final List lore) { + item.editMeta(meta -> { + final var list = lore + .stream() + .map(x -> x.decoration(TextDecoration.ITALIC, false)) + .collect(Collectors.toList()); + meta.lore(list); + }); + + return item; + } + + public static ItemStack name_item(final ItemStack item, Component name, final List lore) { + final var meta = item.getItemMeta(); + + name = name.decoration(TextDecoration.ITALIC, false); + meta.displayName(name); + + if (lore != null) { + final var list = lore + .stream() + .map(x -> x.decoration(TextDecoration.ITALIC, false)) + .collect(Collectors.toList()); + meta.lore(list); + } + + item.setItemMeta(meta); + return item; + } + + public static int compare_enchantments(final ItemStack item_a, final ItemStack item_b) { + var ae = item_a.getEnchantments(); + var be = item_b.getEnchantments(); + + final var a_meta = item_a.getItemMeta(); + if (a_meta instanceof EnchantmentStorageMeta) { + final var stored = ((EnchantmentStorageMeta) a_meta).getStoredEnchants(); + if (stored.size() > 0) { + ae = stored; + } + } + + final var b_meta = item_b.getItemMeta(); + if (b_meta instanceof EnchantmentStorageMeta) { + final var stored = ((EnchantmentStorageMeta) b_meta).getStoredEnchants(); + if (stored.size() > 0) { + be = stored; + } + } + + // Unenchanted first + final var a_count = ae.size(); + final var b_count = be.size(); + if (a_count == 0 && b_count == 0) { + return 0; + } else if (a_count == 0) { + return -1; + } else if (b_count == 0) { + return 1; + } + + // More enchantments before fewer enchantments + if (a_count != b_count) { + return b_count - a_count; + } + + final var a_sorted = ae + .entrySet() + .stream() + .sorted( + Map.Entry.comparingByKey((a, b) -> + a.getKey().toString().compareTo(b.getKey().toString()) + ).thenComparing(Map.Entry.comparingByValue()) + ) + .toList(); + final var b_sorted = be + .entrySet() + .stream() + .sorted( + Map.Entry.comparingByKey((a, b) -> + a.getKey().toString().compareTo(b.getKey().toString()) + ).thenComparing(Map.Entry.comparingByValue()) + ) + .toList(); + + // Lastly, compare names and levels + final var ait = a_sorted.iterator(); + final var bit = b_sorted.iterator(); + + while (ait.hasNext()) { + final var a_el = ait.next(); + final var b_el = bit.next(); + + // Lexicographic name comparison + final var name_diff = a_el.getKey().getKey().toString().compareTo(b_el.getKey().getKey().toString()); + if (name_diff != 0) { + return name_diff; + } + + // Level + final int level_diff = b_el.getValue() - a_el.getValue(); + if (level_diff != 0) { + return level_diff; + } + } + + return 0; + } + + public static class ItemStackComparator implements Comparator { + + @Override + public int compare(final ItemStack a, final ItemStack b) { + if (a == null && b == null) { + return 0; + } else if (a == null) { + return 1; + } else if (b == null) { + return -1; + } + + final var na = item_handle(a); + final var nb = item_handle(b); + if (na.isEmpty()) { + return nb.isEmpty() ? 0 : 1; + } else if (nb.isEmpty()) { + return -1; + } + + // By creative mode tab + final var creative_mode_tab_diff = creative_tab_id(na) - creative_tab_id(nb); + if (creative_mode_tab_diff != 0) { + return creative_mode_tab_diff; + } + + // By id + final var id_diff = Item.getId(na.getItem()) - Item.getId(nb.getItem()); + if (id_diff != 0) { + return id_diff; + } + + // By damage + final var damage_diff = na.getDamageValue() - nb.getDamageValue(); + if (damage_diff != 0) { + return damage_diff; + } + + // By count + final var count_diff = nb.getCount() - na.getCount(); + if (count_diff != 0) { + return count_diff; + } + + // By enchantments + return compare_enchantments(a, b); + } + } + + public static ItemStack skull_for_player(final OfflinePlayer player, final boolean is_for_menu) { + final var item = new ItemStack(Material.PLAYER_HEAD); + if (!is_for_menu || Core.instance().config_player_heads_in_menus) { + item.editMeta(SkullMeta.class, meta -> meta.setOwningPlayer(player)); + } + return item; + } + + public static ItemStack skull_with_texture(final String name, final String base64_texture) { + final var profile = Bukkit.createProfileExact(SKULL_OWNER, "-"); + profile.setProperty(new ProfileProperty("textures", base64_texture)); + + final var item = new ItemStack(Material.PLAYER_HEAD); + final var meta = (SkullMeta) item.getItemMeta(); + final var name_component = Component.text(name) + .decoration(TextDecoration.ITALIC, false) + .color(NamedTextColor.YELLOW); + meta.displayName(name_component); + meta.setPlayerProfile(profile); + item.setItemMeta(meta); + return item; + } + + /** Returns true if the given component is guarded by the given sentinel. */ + public static boolean has_sentinel(final Component component, final NamespacedKey sentiel) { + if (component == null) { + return false; + } + + final var hover = component.hoverEvent(); + if (hover == null) { + return false; + } + + if (hover.value() instanceof final TextComponent hover_text) { + return hover.action() == SHOW_TEXT && sentiel.toString().equals(hover_text.content()); + } else { + return false; + } + } + + public static Component add_sentinel(final Component component, final NamespacedKey sentinel) { + return component.hoverEvent(HoverEvent.showText(Component.text(sentinel.toString()))); + } + + /** + * Applies enchantments to the item given in the form + * "{[*][,[*]]...}". Throws + * IllegalArgumentException if an enchantment cannot be found. + */ + private static ItemStack apply_enchants(final ItemStack item_stack, @Nullable String enchants) { + if (enchants == null) { + return item_stack; + } + + enchants = enchants.trim(); + if (!enchants.startsWith("{") || !enchants.endsWith("}")) { + throw new IllegalArgumentException( + "enchantments must be of form {[*][,[*]]...}" + ); + } + + final var parts = enchants.substring(1, enchants.length() - 1).split(","); + for (var part : parts) { + part = part.trim(); + + String key = part; + int level = 1; + final int level_delim = key.indexOf('*'); + if (level_delim != -1) { + level = Integer.parseInt(key.substring(level_delim + 1)); + key = key.substring(0, level_delim); + } + + final var ench = RegistryAccess.registryAccess() + .getRegistry(RegistryKey.ENCHANTMENT) + .get(NamespacedKey.fromString(key)); + if (ench == null) { + throw new IllegalArgumentException( + "Cannot apply unknown enchantment '" + key + "' to item '" + item_stack + "'" + ); + } + + if (item_stack.getType() == Material.ENCHANTED_BOOK) { + final var flevel = level; + item_stack.editMeta(EnchantmentStorageMeta.class, meta -> meta.addStoredEnchant(ench, flevel, false)); + } else { + item_stack.addEnchantment(ench, level); + } + } + + if (parts.length > 0) { + Core.instance().enchantment_manager.update_enchanted_item(item_stack); + } + return item_stack; + } + + /** Returns the itemstack and a boolean indicating whether it was just as simlpe material. */ + public static @NotNull Pair itemstack_from_string(String definition) { + // namespace:key[[components]][#enchants{}], where the key can reference a + // material, head material or customitem. + final var enchants_delim = definition.indexOf("#enchants{"); + String enchants = null; + if (enchants_delim != -1) { + enchants = definition.substring(enchants_delim + 9); // Let it start at '{' + definition = definition.substring(0, enchants_delim); + } + + final var nbt_delim = definition.indexOf('['); + NamespacedKey key; + if (nbt_delim == -1) { + key = NamespacedKey.fromString(definition); + } else { + key = NamespacedKey.fromString(definition.substring(0, nbt_delim)); + } + + final var emat = ExtendedMaterial.from(key); + if (emat == null) { + throw new IllegalArgumentException("Invalid extended material definition: " + definition); + } + + // First, create the itemstack as if we had no NBT information. + final var item_stack = emat.item(); + + // If there is no NBT information, we can return here. + if (nbt_delim == -1) { + return Pair.of(apply_enchants(item_stack, enchants), emat.is_simple_material() && enchants == null); + } + + // Parse the NBT by using minecraft's internal parser with the base material + // of whatever the extended material gave us. + final var vanilla_definition = item_stack.getType().key() + definition.substring(nbt_delim); + try { + final var parsed_nbt = new ItemParser(Commands.createValidationContext(VanillaRegistries.createLookup())) + .parse(new StringReader(vanilla_definition)) + .components(); + + // Now apply the NBT be parsed by minecraft's internal parser to the itemstack. + final var nms_item = item_handle(item_stack).copy(); + nms_item.applyComponents(parsed_nbt); + + return Pair.of(apply_enchants(CraftItemStack.asCraftMirror(nms_item), enchants), false); + } catch (final CommandSyntaxException e) { + throw new IllegalArgumentException("Could not parse NBT of item definition: " + definition, e); + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/LazyBlock.java b/vane-core/src/main/java/org/oddlama/vane/util/LazyBlock.java index 4f8b45771..cd180b5fa 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/LazyBlock.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/LazyBlock.java @@ -1,62 +1,61 @@ package org.oddlama.vane.util; import java.util.UUID; - import org.bukkit.Bukkit; import org.bukkit.block.Block; public class LazyBlock { - private final UUID world_id; - private int x; - private int y; - private int z; - private Block block; - - public LazyBlock(final Block block) { - if (block == null) { - this.world_id = null; - this.x = 0; - this.y = 0; - this.z = 0; - } else { - this.world_id = block.getWorld().getUID(); - this.x = block.getX(); - this.y = block.getY(); - this.z = block.getZ(); - } - this.block = block; - } - - public LazyBlock(final UUID world_id, int x, int y, int z) { - this.world_id = world_id; - this.x = x; - this.y = y; - this.z = z; - this.block = null; - } - - public UUID world_id() { - return world_id; - } - - public int x() { - return x; - } - - public int y() { - return y; - } - - public int z() { - return z; - } - - public Block block() { - if (world_id != null && block == null) { - this.block = Bukkit.getWorld(world_id).getBlockAt(x, y, z); - } - - return block; - } + private final UUID world_id; + private int x; + private int y; + private int z; + private Block block; + + public LazyBlock(final Block block) { + if (block == null) { + this.world_id = null; + this.x = 0; + this.y = 0; + this.z = 0; + } else { + this.world_id = block.getWorld().getUID(); + this.x = block.getX(); + this.y = block.getY(); + this.z = block.getZ(); + } + this.block = block; + } + + public LazyBlock(final UUID world_id, int x, int y, int z) { + this.world_id = world_id; + this.x = x; + this.y = y; + this.z = z; + this.block = null; + } + + public UUID world_id() { + return world_id; + } + + public int x() { + return x; + } + + public int y() { + return y; + } + + public int z() { + return z; + } + + public Block block() { + if (world_id != null && block == null) { + this.block = Bukkit.getWorld(world_id).getBlockAt(x, y, z); + } + + return block; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/LazyLocation.java b/vane-core/src/main/java/org/oddlama/vane/util/LazyLocation.java index 2833acf5c..ae88ceb8f 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/LazyLocation.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/LazyLocation.java @@ -1,34 +1,33 @@ package org.oddlama.vane.util; import java.util.UUID; - import org.bukkit.Bukkit; import org.bukkit.Location; public class LazyLocation { - private final UUID world_id; - private Location location; + private final UUID world_id; + private Location location; - public LazyLocation(final Location location) { - this.world_id = location.getWorld() == null ? null : location.getWorld().getUID(); - this.location = location.clone(); - } + public LazyLocation(final Location location) { + this.world_id = location.getWorld() == null ? null : location.getWorld().getUID(); + this.location = location.clone(); + } - public LazyLocation(final UUID world_id, double x, double y, double z, float pitch, float yaw) { - this.world_id = world_id; - this.location = new Location(null, x, y, z, pitch, yaw); - } + public LazyLocation(final UUID world_id, double x, double y, double z, float pitch, float yaw) { + this.world_id = world_id; + this.location = new Location(null, x, y, z, pitch, yaw); + } - public UUID world_id() { - return world_id; - } + public UUID world_id() { + return world_id; + } - public Location location() { - if (world_id != null && location.getWorld() == null) { - location.setWorld(Bukkit.getWorld(world_id)); - } + public Location location() { + if (world_id != null && location.getWorld() == null) { + location.setWorld(Bukkit.getWorld(world_id)); + } - return location; - } + return location; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/MaterialUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/MaterialUtil.java index 00c70a5f0..a2f651aa6 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/MaterialUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/MaterialUtil.java @@ -6,72 +6,72 @@ public class MaterialUtil { - public static Material material_from(NamespacedKey key) { - return Registry.MATERIAL.get(key); - } + public static Material material_from(NamespacedKey key) { + return Registry.MATERIAL.get(key); + } - public static boolean is_seeded_plant(Material type) { - switch (type) { - default: - return false; - case WHEAT: - case CARROTS: - case POTATOES: - case BEETROOTS: - case NETHER_WART: - return true; - } - } + public static boolean is_seeded_plant(Material type) { + switch (type) { + default: + return false; + case WHEAT: + case CARROTS: + case POTATOES: + case BEETROOTS: + case NETHER_WART: + return true; + } + } - public static Material seed_for(Material plant_type) { - switch (plant_type) { - default: - return null; - case WHEAT: - return Material.WHEAT_SEEDS; - case CARROTS: - return Material.CARROT; - case POTATOES: - return Material.POTATO; - case BEETROOTS: - return Material.BEETROOT_SEEDS; - case NETHER_WART: - return Material.NETHER_WART; - } - } + public static Material seed_for(Material plant_type) { + switch (plant_type) { + default: + return null; + case WHEAT: + return Material.WHEAT_SEEDS; + case CARROTS: + return Material.CARROT; + case POTATOES: + return Material.POTATO; + case BEETROOTS: + return Material.BEETROOT_SEEDS; + case NETHER_WART: + return Material.NETHER_WART; + } + } - public static Material farmland_for(Material seed_type) { - switch (seed_type) { - default: - return null; - case WHEAT_SEEDS: - case CARROT: - case POTATO: - case BEETROOT_SEEDS: - return Material.FARMLAND; - case NETHER_WART: - return Material.SOUL_SAND; - } - } + public static Material farmland_for(Material seed_type) { + switch (seed_type) { + default: + return null; + case WHEAT_SEEDS: + case CARROT: + case POTATO: + case BEETROOT_SEEDS: + return Material.FARMLAND; + case NETHER_WART: + return Material.SOUL_SAND; + } + } - public static boolean is_replaceable_grass(Material type) { - switch (type) { - default: - return false; - case TALL_GRASS: - case SHORT_GRASS: - return true; - } - } + public static boolean is_replaceable_grass(Material type) { + switch (type) { + default: + return false; + case TALL_GRASS: + case SHORT_GRASS: + return true; + } + } - public static boolean is_tillable(Material type) { - switch (type) { - default: - return false; - case DIRT: - case GRASS_BLOCK: - case DIRT_PATH: - return true; - } - } + public static boolean is_tillable(Material type) { + switch (type) { + default: + return false; + case DIRT: + case GRASS_BLOCK: + case DIRT_PATH: + return true; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/Nms.java b/vane-core/src/main/java/org/oddlama/vane/util/Nms.java index 899297e9d..969b35331 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/Nms.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/Nms.java @@ -1,23 +1,8 @@ package org.oddlama.vane.util; -import java.util.Map; - import com.mojang.datafixers.DataFixUtils; import com.mojang.datafixers.types.Type; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.CraftServer; -import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.enchantments.CraftEnchantment; -import org.bukkit.craftbukkit.entity.CraftEntity; -import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.craftbukkit.inventory.CraftItemStack; -import org.bukkit.enchantments.EnchantmentTarget; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - +import java.util.Map; import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; @@ -35,144 +20,157 @@ import net.minecraft.world.Clearable; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.Enchantment; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.enchantments.CraftEnchantment; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; public class Nms { - public static ServerPlayer get_player(Player player) { - return ((CraftPlayer) player).getHandle(); - } - - public static Entity entity_handle(final org.bukkit.entity.Entity entity) { - return ((CraftEntity) entity).getHandle(); - } - - public static org.bukkit.enchantments.Enchantment bukkit_enchantment(Enchantment enchantment) { - return CraftEnchantment.minecraftToBukkit(enchantment); - } - - @NotNull - public static org.bukkit.inventory.ItemStack bukkit_item_stack(ItemStack stack) { - return CraftItemStack.asCraftMirror(stack); - } - - public static TagKey enchantment_slot_type(EnchantmentTarget target) { - switch (target) { - case ARMOR: - return ItemTags.ARMOR_ENCHANTABLE; - case ARMOR_FEET: - return ItemTags.FOOT_ARMOR_ENCHANTABLE; - case ARMOR_HEAD: - return ItemTags.HEAD_ARMOR_ENCHANTABLE; - case ARMOR_LEGS: - return ItemTags.LEG_ARMOR_ENCHANTABLE; - case ARMOR_TORSO: - return ItemTags.CHEST_ARMOR_ENCHANTABLE; - case TOOL: - return ItemTags.MINING_ENCHANTABLE; - case WEAPON: - return ItemTags.WEAPON_ENCHANTABLE; - case BOW: - return ItemTags.BOW_ENCHANTABLE; - case FISHING_ROD: - return ItemTags.FISHING_ENCHANTABLE; - case BREAKABLE: - return ItemTags.DURABILITY_ENCHANTABLE; - case WEARABLE: - return ItemTags.EQUIPPABLE_ENCHANTABLE; - case TRIDENT: - return ItemTags.TRIDENT_ENCHANTABLE; - case CROSSBOW: - return ItemTags.CROSSBOW_ENCHANTABLE; - case VANISHABLE: - return ItemTags.VANISHING_ENCHANTABLE; - default: - return null; - } - } - - public static ItemStack item_handle(org.bukkit.inventory.ItemStack item_stack) { - if (item_stack == null) { - return null; - } - - if (!(item_stack instanceof CraftItemStack)) { - return CraftItemStack.asNMSCopy(item_stack); - } - - try { - final var handle = CraftItemStack.class.getDeclaredField("handle"); - handle.setAccessible(true); - return (ItemStack) handle.get(item_stack); - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - } - - public static ServerPlayer player_handle(org.bukkit.entity.Player player) { - if (!(player instanceof CraftPlayer)) { - return null; - } - return ((CraftPlayer) player).getHandle(); - } - - public static ServerLevel world_handle(org.bukkit.World world) { - return ((CraftWorld) world).getHandle(); - } - - public static DedicatedServer server_handle() { - final var bukkit_server = Bukkit.getServer(); - return ((CraftServer) bukkit_server).getServer(); - } - - @SuppressWarnings({ "unchecked", "deprecation" }) - public static void register_entity( - final NamespacedKey base_entity_type, - final String pseudo_namespace, - final String key, - final EntityType.Builder builder) { - final var id = pseudo_namespace + "_" + key; - // From: - // https://papermc.io/forums/t/register-and-spawn-a-custom-entity-on-1-13-x/293, - // adapted for 1.18 - // Get the datafixer - final var world_version = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - final var world_version_key = DataFixUtils.makeKey(world_version); - final var data_types = DataFixers - .getDataFixer() - .getSchema(world_version_key) - .findChoiceType(References.ENTITY) - .types(); - final var data_types_map = (Map>) data_types; - // Inject the new custom entity (this registers the key/id with the server, - // so it will be available in vanilla constructs like the /summon command) - data_types_map.put("minecraft:" + id, data_types_map.get(base_entity_type.toString())); - // Store a new type in registry - final var rk = ResourceKey.create(Registries.ENTITY_TYPE, ResourceLocation.withDefaultNamespace(id)); - Registry.register(BuiltInRegistries.ENTITY_TYPE, id, builder.build(rk)); - } - - public static void spawn(org.bukkit.World world, Entity entity) { - world_handle(world).addFreshEntity(entity); - } - - public static int unlock_all_recipes(final org.bukkit.entity.Player player) { - final var recipes = server_handle().getRecipeManager().getRecipes(); - return player_handle(player).awardRecipes(recipes); - } - - public static int creative_tab_id(final ItemStack item_stack) { - // TODO FIXME BUG this is broken and always returns 0 - return (int) CreativeModeTabs.allTabs().stream().takeWhile(tab -> tab.contains(item_stack)).count(); - } - - public static void set_air_no_drops(final org.bukkit.block.Block block) { - final var entity = world_handle(block.getWorld()) - .getBlockEntity(new BlockPos(block.getX(), block.getY(), block.getZ())); - Clearable.tryClear(entity); - block.setType(Material.AIR, false); - } + public static ServerPlayer get_player(Player player) { + return ((CraftPlayer) player).getHandle(); + } + + public static Entity entity_handle(final org.bukkit.entity.Entity entity) { + return ((CraftEntity) entity).getHandle(); + } + + public static org.bukkit.enchantments.Enchantment bukkit_enchantment(Enchantment enchantment) { + return CraftEnchantment.minecraftToBukkit(enchantment); + } + + @NotNull + public static org.bukkit.inventory.ItemStack bukkit_item_stack(ItemStack stack) { + return CraftItemStack.asCraftMirror(stack); + } + + public static TagKey enchantment_slot_type(EnchantmentTarget target) { + switch (target) { + case ARMOR: + return ItemTags.ARMOR_ENCHANTABLE; + case ARMOR_FEET: + return ItemTags.FOOT_ARMOR_ENCHANTABLE; + case ARMOR_HEAD: + return ItemTags.HEAD_ARMOR_ENCHANTABLE; + case ARMOR_LEGS: + return ItemTags.LEG_ARMOR_ENCHANTABLE; + case ARMOR_TORSO: + return ItemTags.CHEST_ARMOR_ENCHANTABLE; + case TOOL: + return ItemTags.MINING_ENCHANTABLE; + case WEAPON: + return ItemTags.WEAPON_ENCHANTABLE; + case BOW: + return ItemTags.BOW_ENCHANTABLE; + case FISHING_ROD: + return ItemTags.FISHING_ENCHANTABLE; + case BREAKABLE: + return ItemTags.DURABILITY_ENCHANTABLE; + case WEARABLE: + return ItemTags.EQUIPPABLE_ENCHANTABLE; + case TRIDENT: + return ItemTags.TRIDENT_ENCHANTABLE; + case CROSSBOW: + return ItemTags.CROSSBOW_ENCHANTABLE; + case VANISHABLE: + return ItemTags.VANISHING_ENCHANTABLE; + default: + return null; + } + } + + public static ItemStack item_handle(org.bukkit.inventory.ItemStack item_stack) { + if (item_stack == null) { + return null; + } + + if (!(item_stack instanceof CraftItemStack)) { + return CraftItemStack.asNMSCopy(item_stack); + } + + try { + final var handle = CraftItemStack.class.getDeclaredField("handle"); + handle.setAccessible(true); + return (ItemStack) handle.get(item_stack); + } catch (NoSuchFieldException | IllegalAccessException e) { + return null; + } + } + + public static ServerPlayer player_handle(org.bukkit.entity.Player player) { + if (!(player instanceof CraftPlayer)) { + return null; + } + return ((CraftPlayer) player).getHandle(); + } + + public static ServerLevel world_handle(org.bukkit.World world) { + return ((CraftWorld) world).getHandle(); + } + + public static DedicatedServer server_handle() { + final var bukkit_server = Bukkit.getServer(); + return ((CraftServer) bukkit_server).getServer(); + } + + @SuppressWarnings({ "unchecked", "deprecation" }) + public static void register_entity( + final NamespacedKey base_entity_type, + final String pseudo_namespace, + final String key, + final EntityType.Builder builder + ) { + final var id = pseudo_namespace + "_" + key; + // From: + // https://papermc.io/forums/t/register-and-spawn-a-custom-entity-on-1-13-x/293, + // adapted for 1.18 + // Get the datafixer + final var world_version = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + final var world_version_key = DataFixUtils.makeKey(world_version); + final var data_types = DataFixers.getDataFixer() + .getSchema(world_version_key) + .findChoiceType(References.ENTITY) + .types(); + final var data_types_map = (Map>) data_types; + // Inject the new custom entity (this registers the key/id with the server, + // so it will be available in vanilla constructs like the /summon command) + data_types_map.put("minecraft:" + id, data_types_map.get(base_entity_type.toString())); + // Store a new type in registry + final var rk = ResourceKey.create(Registries.ENTITY_TYPE, ResourceLocation.withDefaultNamespace(id)); + Registry.register(BuiltInRegistries.ENTITY_TYPE, id, builder.build(rk)); + } + + public static void spawn(org.bukkit.World world, Entity entity) { + world_handle(world).addFreshEntity(entity); + } + + public static int unlock_all_recipes(final org.bukkit.entity.Player player) { + final var recipes = server_handle().getRecipeManager().getRecipes(); + return player_handle(player).awardRecipes(recipes); + } + + public static int creative_tab_id(final ItemStack item_stack) { + // TODO FIXME BUG this is broken and always returns 0 + return (int) CreativeModeTabs.allTabs().stream().takeWhile(tab -> tab.contains(item_stack)).count(); + } + + public static void set_air_no_drops(final org.bukkit.block.Block block) { + final var entity = world_handle(block.getWorld()).getBlockEntity( + new BlockPos(block.getX(), block.getY(), block.getZ()) + ); + Clearable.tryClear(entity); + block.setType(Material.AIR, false); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/PlayerUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/PlayerUtil.java index 434d89bbc..0f924522e 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/PlayerUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/PlayerUtil.java @@ -6,7 +6,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; - import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Material; @@ -23,245 +22,247 @@ public class PlayerUtil { - public static void apply_elytra_boost(final Player player, double factor) { - final var v = player.getLocation().getDirection(); - v.normalize(); - v.multiply(factor); - - // Set velocity, play sound - player.setVelocity(player.getVelocity().add(v)); - player - .getWorld() - .playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, SoundCategory.PLAYERS, 0.4f, 2.0f); - } - - public static void remove_one_item_from_hand(final Player player, final EquipmentSlot hand) { - final var item = player.getEquipment().getItem(hand); - if (item.getAmount() == 1) { - player.getInventory().setItem(hand, null); - } else { - item.setAmount(item.getAmount() - 1); - player.getInventory().setItem(hand, item); - } - } - - // ItemStack amounts are discarded, only the mapped value counts. - // CAUTION: There must not be duplicate item keys that could stack. - public static boolean has_items(final Player player, final Map items) { - if (player.getGameMode() == GameMode.CREATIVE) { - return true; - } - - final var inventory = player.getInventory(); - for (final var e : items.entrySet()) { - final var item = e.getKey().clone(); - item.setAmount(1); - final var amount = e.getValue(); - if (!inventory.containsAtLeast(item, amount)) { - return false; - } - } - - return true; - } - - public static boolean take_items(final Player player, final ItemStack item) { - final var map = new HashMap(); - map.put(item, item.getAmount()); - return take_items(player, map); - } - - public static boolean take_items(final Player player, final Map items) { - if (player.getGameMode() == GameMode.CREATIVE) { - return true; - } - - if (!has_items(player, items)) { - return false; - } - - final var inventory = player.getInventory(); - final var stacks = new ArrayList(); - for (final var e : items.entrySet()) { - stacks.addAll(Arrays.asList(create_lawful_stacks(e.getKey(), e.getValue()))); - } - - final var leftovers = inventory.removeItem(stacks.toArray(new ItemStack[0])); - if (!leftovers.isEmpty()) { - Bukkit - .getLogger() - .warning( - "[vane] Unexpected leftovers while removing the following items from a player's inventory: " - + - stacks); - for (final var l : leftovers.entrySet()) { - Bukkit.getLogger().warning("[vane] Leftover: " + l.getKey() + ", amount: " + l.getValue()); - } - return false; - } - - return true; - } - - public static void give_item(final Player player, final ItemStack item) { - give_items(player, new ItemStack[] { item }); - } - - // Ignores item.getAmount(). - public static ItemStack[] create_lawful_stacks(final ItemStack item, int amount) { - final var stacks = (item.getMaxStackSize() - 1 + amount) / item.getMaxStackSize(); - final var leftover = amount % item.getMaxStackSize(); - if (stacks < 1) { - return new ItemStack[] {}; - } - - final var items = new ItemStack[stacks]; - for (int i = 0; i < stacks; ++i) { - items[i] = item.clone(); - items[i].setAmount(item.getMaxStackSize()); - } - if (leftover != 0) { - items[stacks - 1].setAmount(leftover); - } - - return items; - } - - public static void give_items(final Player player, final ItemStack item, int amount) { - give_items(player, create_lawful_stacks(item, amount)); - } - - public static void give_items(final Player player, final ItemStack[] items) { - final var leftovers = player.getInventory().addItem(items); - for (final var item : leftovers.values()) { - player.getLocation().getWorld().dropItem(player.getLocation(), item).setPickupDelay(0); - } - } - - public static boolean till_block(final Player player, final Block block) { - // Create block break event for block to till and check if it gets canceled - final var break_event = new BlockBreakEvent(block, player); - Bukkit.getPluginManager().callEvent(break_event); - if (break_event.isCancelled()) { - return false; - } - - // Till block - block.setType(Material.FARMLAND); - - // Play sound - player.getWorld().playSound(player.getLocation(), Sound.ITEM_HOE_TILL, SoundCategory.BLOCKS, 1.0f, 1.0f); - return true; - } - - public static boolean seed_block( - final Player player, - final ItemStack used_item, - final Block block, - final Material plant_type, - final Material seed_type) { - // Create block place event for seed to place and check if it gets canceled - final var below = block.getRelative(BlockFace.DOWN); - final var place_event = new BlockPlaceEvent( - block, - below.getState(), - below, - used_item, - player, - true, - EquipmentSlot.HAND); - Bukkit.getPluginManager().callEvent(place_event); - if (place_event.isCancelled()) { - return false; - } - - // Remove one seed from inventory if not in creative mode - if (player.getGameMode() != GameMode.CREATIVE) { - final var seedstack = new ItemStack(seed_type, 1); - if (!player.getInventory().containsAtLeast(seedstack, 1)) { - return false; - } - - player.getInventory().removeItem(seedstack); - } - - // Set block seeded - block.setType(plant_type); - final var ageable = (Ageable) block.getBlockData(); - ageable.setAge(0); - block.setBlockData(ageable); - - // Play sound - player - .getWorld() - .playSound( - player.getLocation(), - seed_type == Material.NETHER_WART ? Sound.ITEM_NETHER_WART_PLANT : Sound.ITEM_CROP_PLANT, - SoundCategory.BLOCKS, - 1.0f, - 1.0f); - return true; - } - - public static boolean harvest_plant(final Player player, final Block block) { - ItemStack[] drops; - switch (block.getType()) { - default: - return false; - case WHEAT: - drops = new ItemStack[] { new ItemStack(Material.WHEAT, 1 + (int) (Math.random() * 2.5)) }; - break; - case CARROTS: - drops = new ItemStack[] { new ItemStack(Material.CARROT, 1 + (int) (Math.random() * 2.5)) }; - break; - case POTATOES: - drops = new ItemStack[] { new ItemStack(Material.POTATO, 1 + (int) (Math.random() * 2.5)) }; - break; - case BEETROOTS: - drops = new ItemStack[] { new ItemStack(Material.BEETROOT, 1 + (int) (Math.random() * 2.5)) }; - break; - case NETHER_WART: - drops = new ItemStack[] { new ItemStack(Material.NETHER_WART, 1 + (int) (Math.random() * 2.5)) }; - break; - } - - if (!(block.getBlockData() instanceof Ageable)) { - return false; - } - - // Only harvest fully grown plants - var ageable = (Ageable) block.getBlockData(); - if (ageable.getAge() != ageable.getMaximumAge()) { - return false; - } - - // Create a block break event for block to harvest and check if it gets canceled - final var break_event = new BlockBreakEvent(block, player); - Bukkit.getPluginManager().callEvent(break_event); - if (break_event.isCancelled()) { - return false; - } - - // Reset crop state - ageable.setAge(0); - block.setBlockData(ageable); - - // Drop items - for (ItemStack drop : drops) { - drop_naturally(block, drop); - } - - return true; - } - - public static void swing_arm(final Player player, final EquipmentSlot hand) { - switch (hand) { - case HAND: - player.swingMainHand(); - break; - case OFF_HAND: - player.swingOffHand(); - break; - } - } + public static void apply_elytra_boost(final Player player, double factor) { + final var v = player.getLocation().getDirection(); + v.normalize(); + v.multiply(factor); + + // Set velocity, play sound + player.setVelocity(player.getVelocity().add(v)); + player + .getWorld() + .playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, SoundCategory.PLAYERS, 0.4f, 2.0f); + } + + public static void remove_one_item_from_hand(final Player player, final EquipmentSlot hand) { + final var item = player.getEquipment().getItem(hand); + if (item.getAmount() == 1) { + player.getInventory().setItem(hand, null); + } else { + item.setAmount(item.getAmount() - 1); + player.getInventory().setItem(hand, item); + } + } + + // ItemStack amounts are discarded, only the mapped value counts. + // CAUTION: There must not be duplicate item keys that could stack. + public static boolean has_items(final Player player, final Map items) { + if (player.getGameMode() == GameMode.CREATIVE) { + return true; + } + + final var inventory = player.getInventory(); + for (final var e : items.entrySet()) { + final var item = e.getKey().clone(); + item.setAmount(1); + final var amount = e.getValue(); + if (!inventory.containsAtLeast(item, amount)) { + return false; + } + } + + return true; + } + + public static boolean take_items(final Player player, final ItemStack item) { + final var map = new HashMap(); + map.put(item, item.getAmount()); + return take_items(player, map); + } + + public static boolean take_items(final Player player, final Map items) { + if (player.getGameMode() == GameMode.CREATIVE) { + return true; + } + + if (!has_items(player, items)) { + return false; + } + + final var inventory = player.getInventory(); + final var stacks = new ArrayList(); + for (final var e : items.entrySet()) { + stacks.addAll(Arrays.asList(create_lawful_stacks(e.getKey(), e.getValue()))); + } + + final var leftovers = inventory.removeItem(stacks.toArray(new ItemStack[0])); + if (!leftovers.isEmpty()) { + Bukkit.getLogger() + .warning( + "[vane] Unexpected leftovers while removing the following items from a player's inventory: " + + stacks + ); + for (final var l : leftovers.entrySet()) { + Bukkit.getLogger().warning("[vane] Leftover: " + l.getKey() + ", amount: " + l.getValue()); + } + return false; + } + + return true; + } + + public static void give_item(final Player player, final ItemStack item) { + give_items(player, new ItemStack[] { item }); + } + + // Ignores item.getAmount(). + public static ItemStack[] create_lawful_stacks(final ItemStack item, int amount) { + final var stacks = (item.getMaxStackSize() - 1 + amount) / item.getMaxStackSize(); + final var leftover = amount % item.getMaxStackSize(); + if (stacks < 1) { + return new ItemStack[] {}; + } + + final var items = new ItemStack[stacks]; + for (int i = 0; i < stacks; ++i) { + items[i] = item.clone(); + items[i].setAmount(item.getMaxStackSize()); + } + if (leftover != 0) { + items[stacks - 1].setAmount(leftover); + } + + return items; + } + + public static void give_items(final Player player, final ItemStack item, int amount) { + give_items(player, create_lawful_stacks(item, amount)); + } + + public static void give_items(final Player player, final ItemStack[] items) { + final var leftovers = player.getInventory().addItem(items); + for (final var item : leftovers.values()) { + player.getLocation().getWorld().dropItem(player.getLocation(), item).setPickupDelay(0); + } + } + + public static boolean till_block(final Player player, final Block block) { + // Create block break event for block to till and check if it gets canceled + final var break_event = new BlockBreakEvent(block, player); + Bukkit.getPluginManager().callEvent(break_event); + if (break_event.isCancelled()) { + return false; + } + + // Till block + block.setType(Material.FARMLAND); + + // Play sound + player.getWorld().playSound(player.getLocation(), Sound.ITEM_HOE_TILL, SoundCategory.BLOCKS, 1.0f, 1.0f); + return true; + } + + public static boolean seed_block( + final Player player, + final ItemStack used_item, + final Block block, + final Material plant_type, + final Material seed_type + ) { + // Create block place event for seed to place and check if it gets canceled + final var below = block.getRelative(BlockFace.DOWN); + final var place_event = new BlockPlaceEvent( + block, + below.getState(), + below, + used_item, + player, + true, + EquipmentSlot.HAND + ); + Bukkit.getPluginManager().callEvent(place_event); + if (place_event.isCancelled()) { + return false; + } + + // Remove one seed from inventory if not in creative mode + if (player.getGameMode() != GameMode.CREATIVE) { + final var seedstack = new ItemStack(seed_type, 1); + if (!player.getInventory().containsAtLeast(seedstack, 1)) { + return false; + } + + player.getInventory().removeItem(seedstack); + } + + // Set block seeded + block.setType(plant_type); + final var ageable = (Ageable) block.getBlockData(); + ageable.setAge(0); + block.setBlockData(ageable); + + // Play sound + player + .getWorld() + .playSound( + player.getLocation(), + seed_type == Material.NETHER_WART ? Sound.ITEM_NETHER_WART_PLANT : Sound.ITEM_CROP_PLANT, + SoundCategory.BLOCKS, + 1.0f, + 1.0f + ); + return true; + } + + public static boolean harvest_plant(final Player player, final Block block) { + ItemStack[] drops; + switch (block.getType()) { + default: + return false; + case WHEAT: + drops = new ItemStack[] { new ItemStack(Material.WHEAT, 1 + (int) (Math.random() * 2.5)) }; + break; + case CARROTS: + drops = new ItemStack[] { new ItemStack(Material.CARROT, 1 + (int) (Math.random() * 2.5)) }; + break; + case POTATOES: + drops = new ItemStack[] { new ItemStack(Material.POTATO, 1 + (int) (Math.random() * 2.5)) }; + break; + case BEETROOTS: + drops = new ItemStack[] { new ItemStack(Material.BEETROOT, 1 + (int) (Math.random() * 2.5)) }; + break; + case NETHER_WART: + drops = new ItemStack[] { new ItemStack(Material.NETHER_WART, 1 + (int) (Math.random() * 2.5)) }; + break; + } + + if (!(block.getBlockData() instanceof Ageable)) { + return false; + } + + // Only harvest fully grown plants + var ageable = (Ageable) block.getBlockData(); + if (ageable.getAge() != ageable.getMaximumAge()) { + return false; + } + + // Create a block break event for block to harvest and check if it gets canceled + final var break_event = new BlockBreakEvent(block, player); + Bukkit.getPluginManager().callEvent(break_event); + if (break_event.isCancelled()) { + return false; + } + + // Reset crop state + ageable.setAge(0); + block.setBlockData(ageable); + + // Drop items + for (ItemStack drop : drops) { + drop_naturally(block, drop); + } + + return true; + } + + public static void swing_arm(final Player player, final EquipmentSlot hand) { + switch (hand) { + case HAND: + player.swingMainHand(); + break; + case OFF_HAND: + player.swingOffHand(); + break; + } + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/Resolve.java b/vane-core/src/main/java/org/oddlama/vane/util/Resolve.java index f300279bf..6f0e1c7dd 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/Resolve.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/Resolve.java @@ -3,35 +3,36 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.UUID; - import org.json.JSONException; public class Resolve { - public static class Skin { - - public String texture; - public String signature; - } - - public static Skin resolve_skin(UUID id) throws IOException, JSONException, URISyntaxException { - final var url = "https://sessionserver.mojang.com/session/minecraft/profile/" + id + "?unsigned=false"; - - final var json = IOUtil.read_json_from_url(url); - final var skin = new Skin(); - final var obj = json.getJSONArray("properties").getJSONObject(0); - skin.texture = obj.getString("value"); - skin.signature = obj.getString("signature"); - return skin; - } - - public static UUID resolve_uuid(String name) throws IOException, JSONException, URISyntaxException { - final var url = "https://api.mojang.com/users/profiles/minecraft/" + name; - - final var json = IOUtil.read_json_from_url(url); - final var id_str = json.getString("id"); - final var uuid_str = id_str.replaceFirst( - "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", - "$1-$2-$3-$4-$5"); - return UUID.fromString(uuid_str); - } + + public static class Skin { + + public String texture; + public String signature; + } + + public static Skin resolve_skin(UUID id) throws IOException, JSONException, URISyntaxException { + final var url = "https://sessionserver.mojang.com/session/minecraft/profile/" + id + "?unsigned=false"; + + final var json = IOUtil.read_json_from_url(url); + final var skin = new Skin(); + final var obj = json.getJSONArray("properties").getJSONObject(0); + skin.texture = obj.getString("value"); + skin.signature = obj.getString("signature"); + return skin; + } + + public static UUID resolve_uuid(String name) throws IOException, JSONException, URISyntaxException { + final var url = "https://api.mojang.com/users/profiles/minecraft/" + name; + + final var json = IOUtil.read_json_from_url(url); + final var id_str = json.getString("id"); + final var uuid_str = id_str.replaceFirst( + "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", + "$1-$2-$3-$4-$5" + ); + return UUID.fromString(uuid_str); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/ResourceList.java b/vane-core/src/main/java/org/oddlama/vane/util/ResourceList.java index 08923b768..e26c21712 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/ResourceList.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/ResourceList.java @@ -8,85 +8,84 @@ import java.util.Collection; import java.util.regex.Pattern; import java.util.zip.ZipEntry; -import java.util.zip.ZipException; import java.util.zip.ZipFile; /** - * from: forums.devx.com/showthread.php?t=153784 - * list resources available from the jar file of the given class + * from: forums.devx.com/showthread.php?t=153784 list resources available from the jar file of the + * given class */ public class ResourceList { - /** - * For all elements of java.class.path get a Collection of resource Pattern - * pattern = Pattern.compile(".*"); gets all resources - * - * @param pattern the pattern to match - * @return the resources in the order they are found - */ - public static Collection get_resources(final Class clazz, final Pattern pattern) { - final var jar_url = clazz.getProtectionDomain().getCodeSource().getLocation(); - try { - return get_resources(new URI(jar_url.toString()).getPath(), pattern); - } catch (URISyntaxException e) { - return new ArrayList(); - } - } + /** + * For all elements of java.class.path get a Collection of resource Pattern pattern = + * Pattern.compile(".*"); gets all resources + * + * @param pattern the pattern to match + * @return the resources in the order they are found + */ + public static Collection get_resources(final Class clazz, final Pattern pattern) { + final var jar_url = clazz.getProtectionDomain().getCodeSource().getLocation(); + try { + return get_resources(new URI(jar_url.toString()).getPath(), pattern); + } catch (URISyntaxException e) { + return new ArrayList(); + } + } - private static Collection get_resources(final String path, final Pattern pattern) { - final var retval = new ArrayList(); - final var file = new File(path); - if (file.isDirectory()) { - retval.addAll(get_resources_from_directory(file, pattern)); - } else { - retval.addAll(get_resources_from_jar_file(file, pattern)); - } - return retval; - } + private static Collection get_resources(final String path, final Pattern pattern) { + final var retval = new ArrayList(); + final var file = new File(path); + if (file.isDirectory()) { + retval.addAll(get_resources_from_directory(file, pattern)); + } else { + retval.addAll(get_resources_from_jar_file(file, pattern)); + } + return retval; + } - private static Collection get_resources_from_jar_file(final File file, final Pattern pattern) { - final var retval = new ArrayList(); - ZipFile zf; - try { - zf = new ZipFile(file); - } catch (final IOException e) { - throw new Error(e); - } - final var e = zf.entries(); - while (e.hasMoreElements()) { - final ZipEntry ze = e.nextElement(); - final String fileName = ze.getName(); - final boolean accept = pattern.matcher(fileName).matches(); - if (accept) { - retval.add(fileName); - } - } - try { - zf.close(); - } catch (final IOException e1) { - throw new Error(e1); - } - return retval; - } + private static Collection get_resources_from_jar_file(final File file, final Pattern pattern) { + final var retval = new ArrayList(); + ZipFile zf; + try { + zf = new ZipFile(file); + } catch (final IOException e) { + throw new Error(e); + } + final var e = zf.entries(); + while (e.hasMoreElements()) { + final ZipEntry ze = e.nextElement(); + final String fileName = ze.getName(); + final boolean accept = pattern.matcher(fileName).matches(); + if (accept) { + retval.add(fileName); + } + } + try { + zf.close(); + } catch (final IOException e1) { + throw new Error(e1); + } + return retval; + } - private static Collection get_resources_from_directory(final File directory, final Pattern pattern) { - final var retval = new ArrayList(); - final var fileList = directory.listFiles(); - for (final File file : fileList) { - if (file.isDirectory()) { - retval.addAll(get_resources_from_directory(file, pattern)); - } else { - try { - final String fileName = file.getCanonicalPath(); - final boolean accept = pattern.matcher(fileName).matches(); - if (accept) { - retval.add(fileName); - } - } catch (final IOException e) { - throw new Error(e); - } - } - } - return retval; - } + private static Collection get_resources_from_directory(final File directory, final Pattern pattern) { + final var retval = new ArrayList(); + final var fileList = directory.listFiles(); + for (final File file : fileList) { + if (file.isDirectory()) { + retval.addAll(get_resources_from_directory(file, pattern)); + } else { + try { + final String fileName = file.getCanonicalPath(); + final boolean accept = pattern.matcher(fileName).matches(); + if (accept) { + retval.add(fileName); + } + } catch (final IOException e) { + throw new Error(e); + } + } + } + return retval; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/StorageUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/StorageUtil.java index c5192ab88..00218d178 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/StorageUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/StorageUtil.java @@ -1,7 +1,6 @@ package org.oddlama.vane.util; import java.util.UUID; - import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.NamespacedKey; @@ -11,53 +10,60 @@ public class StorageUtil { - @SuppressWarnings("deprecation") - public static NamespacedKey namespaced_key(String namespace, String key) { - return new NamespacedKey(namespace, key); - } - - public static NamespacedKey subkey(NamespacedKey key, String sub) { - return namespaced_key(key.namespace(), key.value() + "." + sub); - } - - public static boolean storage_has_location(@NotNull PersistentDataContainer data, NamespacedKey key) { - return data.has(subkey(key, "world"), PersistentDataType.STRING); - } - - public static Location storage_get_location(@NotNull PersistentDataContainer data, NamespacedKey key, Location def) { - try { - final var world_id = data.get(subkey(key, "world"), PersistentDataType.STRING); - final var x = data.get(subkey(key, "x"), PersistentDataType.DOUBLE); - final var y = data.get(subkey(key, "y"), PersistentDataType.DOUBLE); - final var z = data.get(subkey(key, "z"), PersistentDataType.DOUBLE); - final var yaw = data.get(subkey(key, "yaw"), PersistentDataType.FLOAT); - final var pitch = data.get(subkey(key, "pitch"), PersistentDataType.FLOAT); - final var world = Bukkit.getWorld(UUID.fromString(world_id)); - if (world == null) { - return def; - } - return new Location(world, x, y, z, yaw, pitch); - } catch (IllegalArgumentException | NullPointerException e) { - return def; - } - } - - public static void storage_remove_location(@NotNull PersistentDataContainer data, NamespacedKey key) { - data.remove(subkey(key, "world")); - data.remove(subkey(key, "x")); - data.remove(subkey(key, "y")); - data.remove(subkey(key, "z")); - data.remove(subkey(key, "yaw")); - data.remove(subkey(key, "pitch")); - } - - public static void storage_set_location(@NotNull PersistentDataContainer data, NamespacedKey key, @NotNull Location location) { - data.set(subkey(key, "world"), PersistentDataType.STRING, location.getWorld().getUID().toString()); - data.set(subkey(key, "x"), PersistentDataType.DOUBLE, location.getX()); - data.set(subkey(key, "y"), PersistentDataType.DOUBLE, location.getY()); - data.set(subkey(key, "z"), PersistentDataType.DOUBLE, location.getZ()); - data.set(subkey(key, "yaw"), PersistentDataType.FLOAT, location.getYaw()); - data.set(subkey(key, "pitch"), PersistentDataType.FLOAT, location.getPitch()); - } + @SuppressWarnings("deprecation") + public static NamespacedKey namespaced_key(String namespace, String key) { + return new NamespacedKey(namespace, key); + } + + public static NamespacedKey subkey(NamespacedKey key, String sub) { + return namespaced_key(key.namespace(), key.value() + "." + sub); + } + + public static boolean storage_has_location(@NotNull PersistentDataContainer data, NamespacedKey key) { + return data.has(subkey(key, "world"), PersistentDataType.STRING); + } + + public static Location storage_get_location( + @NotNull PersistentDataContainer data, + NamespacedKey key, + Location def + ) { + try { + final var world_id = data.get(subkey(key, "world"), PersistentDataType.STRING); + final var x = data.get(subkey(key, "x"), PersistentDataType.DOUBLE); + final var y = data.get(subkey(key, "y"), PersistentDataType.DOUBLE); + final var z = data.get(subkey(key, "z"), PersistentDataType.DOUBLE); + final var yaw = data.get(subkey(key, "yaw"), PersistentDataType.FLOAT); + final var pitch = data.get(subkey(key, "pitch"), PersistentDataType.FLOAT); + final var world = Bukkit.getWorld(UUID.fromString(world_id)); + if (world == null) { + return def; + } + return new Location(world, x, y, z, yaw, pitch); + } catch (IllegalArgumentException | NullPointerException e) { + return def; + } + } + + public static void storage_remove_location(@NotNull PersistentDataContainer data, NamespacedKey key) { + data.remove(subkey(key, "world")); + data.remove(subkey(key, "x")); + data.remove(subkey(key, "y")); + data.remove(subkey(key, "z")); + data.remove(subkey(key, "yaw")); + data.remove(subkey(key, "pitch")); + } + public static void storage_set_location( + @NotNull PersistentDataContainer data, + NamespacedKey key, + @NotNull Location location + ) { + data.set(subkey(key, "world"), PersistentDataType.STRING, location.getWorld().getUID().toString()); + data.set(subkey(key, "x"), PersistentDataType.DOUBLE, location.getX()); + data.set(subkey(key, "y"), PersistentDataType.DOUBLE, location.getY()); + data.set(subkey(key, "z"), PersistentDataType.DOUBLE, location.getZ()); + data.set(subkey(key, "yaw"), PersistentDataType.FLOAT, location.getYaw()); + data.set(subkey(key, "pitch"), PersistentDataType.FLOAT, location.getPitch()); + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/TimeUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/TimeUtil.java index a85e9419d..02b05b535 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/TimeUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/TimeUtil.java @@ -4,64 +4,65 @@ import java.util.Map; public class TimeUtil { - private static Map time_multiplier; - - static { - Map mult = new HashMap<>(); - mult.put('s', 1000L); // seconds - mult.put('m', 60000L); // minutes - mult.put('h', 3600000L); // hours - mult.put('d', 86400000L); // days - mult.put('w', 604800000L); // weeks - mult.put('y', 31536000000L); // years - time_multiplier = mult; - } - - public static long parse_time(String input) throws NumberFormatException { - long ret = 0; - - for (String time : input.split("(?<=[^0-9])(?=[0-9])")) { - String content[] = time.split("(?=[^0-9])"); - - if (content.length != 2) { - throw new NumberFormatException("missing multiplier"); - } - - Long mult = time_multiplier.get(content[1].replace("and", "").replaceAll("[,+\\.\\s]+", "").charAt(0)); - if (mult == null) { - throw new NumberFormatException("\"" + content[1] + "\" is not a valid multiplier"); - } - - ret += Long.parseLong(content[0]) * mult; - } - - return ret; - } - - public static String format_time(long millis) { - String ret = ""; - - long days = millis / 86400000L; - long hours = (millis / 3600000L) % 24; - long minutes = (millis / 60000L) % 60; - long seconds = (millis / 1000L) % 60; - - if (days > 0) { - ret += days + "d"; - } - - if (hours > 0) { - ret += hours + "h"; - } - - if (minutes > 0) { - ret += minutes + "m"; - } - - if (seconds > 0 || ret.length() == 0) { - ret += seconds + "s"; - } - - return ret; - } + + private static Map time_multiplier; + + static { + Map mult = new HashMap<>(); + mult.put('s', 1000L); // seconds + mult.put('m', 60000L); // minutes + mult.put('h', 3600000L); // hours + mult.put('d', 86400000L); // days + mult.put('w', 604800000L); // weeks + mult.put('y', 31536000000L); // years + time_multiplier = mult; + } + + public static long parse_time(String input) throws NumberFormatException { + long ret = 0; + + for (String time : input.split("(?<=[^0-9])(?=[0-9])")) { + String content[] = time.split("(?=[^0-9])"); + + if (content.length != 2) { + throw new NumberFormatException("missing multiplier"); + } + + Long mult = time_multiplier.get(content[1].replace("and", "").replaceAll("[,+\\.\\s]+", "").charAt(0)); + if (mult == null) { + throw new NumberFormatException("\"" + content[1] + "\" is not a valid multiplier"); + } + + ret += Long.parseLong(content[0]) * mult; + } + + return ret; + } + + public static String format_time(long millis) { + String ret = ""; + + long days = millis / 86400000L; + long hours = (millis / 3600000L) % 24; + long minutes = (millis / 60000L) % 60; + long seconds = (millis / 1000L) % 60; + + if (days > 0) { + ret += days + "d"; + } + + if (hours > 0) { + ret += hours + "h"; + } + + if (minutes > 0) { + ret += minutes + "m"; + } + + if (seconds > 0 || ret.length() == 0) { + ret += seconds + "s"; + } + + return ret; + } } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/Version.java b/vane-core/src/main/java/org/oddlama/vane/util/Version.java index 254c3d511..59ffa1879 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/Version.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/Version.java @@ -2,6 +2,5 @@ public final class Version { - public static final String VERSION = "$VERSION"; - + public static final String VERSION = "$VERSION"; } diff --git a/vane-core/src/main/java/org/oddlama/vane/util/WorldUtil.java b/vane-core/src/main/java/org/oddlama/vane/util/WorldUtil.java index 6fa2e1285..7d51dedcc 100644 --- a/vane-core/src/main/java/org/oddlama/vane/util/WorldUtil.java +++ b/vane-core/src/main/java/org/oddlama/vane/util/WorldUtil.java @@ -2,70 +2,71 @@ import java.util.HashMap; import java.util.UUID; - import org.bukkit.World; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitTask; public class WorldUtil { - private static final HashMap running_time_change_tasks = new HashMap<>(); + private static final HashMap running_time_change_tasks = new HashMap<>(); - public static boolean change_time_smoothly( - final World world, - final Plugin plugin, - final long world_ticks, - final long interpolation_ticks) { - synchronized (running_time_change_tasks) { - if (running_time_change_tasks.containsKey(world.getUID())) { - return false; - } + public static boolean change_time_smoothly( + final World world, + final Plugin plugin, + final long world_ticks, + final long interpolation_ticks + ) { + synchronized (running_time_change_tasks) { + if (running_time_change_tasks.containsKey(world.getUID())) { + return false; + } - // Calculate relative time from and to - var rel_to = world_ticks; - var rel_from = world.getTime(); - if (rel_to <= rel_from) { - rel_to += 24000; - } + // Calculate relative time from and to + var rel_to = world_ticks; + var rel_from = world.getTime(); + if (rel_to <= rel_from) { + rel_to += 24000; + } - // Calculate absolute values - final var delta_ticks = rel_to - rel_from; - final var absolute_from = world.getFullTime(); - final var absolute_to = absolute_from - rel_from + rel_to; + // Calculate absolute values + final var delta_ticks = rel_to - rel_from; + final var absolute_from = world.getFullTime(); + final var absolute_to = absolute_from - rel_from + rel_to; - // Task to advance time every tick - BukkitTask task = plugin - .getServer() - .getScheduler() - .runTaskTimer( - plugin, - new Runnable() { - private long elapsed = 0; + // Task to advance time every tick + BukkitTask task = plugin + .getServer() + .getScheduler() + .runTaskTimer( + plugin, + new Runnable() { + private long elapsed = 0; - @Override - public void run() { - // Remove a task if we finished interpolation - if (elapsed > interpolation_ticks) { - synchronized (running_time_change_tasks) { - running_time_change_tasks.remove(world.getUID()).cancel(); - } - } + @Override + public void run() { + // Remove a task if we finished interpolation + if (elapsed > interpolation_ticks) { + synchronized (running_time_change_tasks) { + running_time_change_tasks.remove(world.getUID()).cancel(); + } + } - // Make the transition smooth by applying a cosine - var lin_delta = (float) elapsed / interpolation_ticks; - var delta = (1f - (float) Math.cos(Math.PI * lin_delta)) / 2f; + // Make the transition smooth by applying a cosine + var lin_delta = (float) elapsed / interpolation_ticks; + var delta = (1f - (float) Math.cos(Math.PI * lin_delta)) / 2f; - var cur_ticks = absolute_from + (long) (delta_ticks * delta); - world.setFullTime(cur_ticks); - ++elapsed; - } - }, - 1, - 1); + var cur_ticks = absolute_from + (long) (delta_ticks * delta); + world.setFullTime(cur_ticks); + ++elapsed; + } + }, + 1, + 1 + ); - running_time_change_tasks.put(world.getUID(), task); - } + running_time_change_tasks.put(world.getUID(), task); + } - return true; - } + return true; + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/CustomEnchantmentRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/CustomEnchantmentRegistry.java index 11b7f1931..46987b792 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/CustomEnchantmentRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/CustomEnchantmentRegistry.java @@ -1,11 +1,5 @@ package org.oddlama.vane.enchantments; -import java.util.List; - -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.EquipmentSlotGroup; -import org.bukkit.inventory.ItemType; - import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.data.EnchantmentRegistryEntry; @@ -13,13 +7,17 @@ import io.papermc.paper.registry.set.RegistryKeySet; import io.papermc.paper.registry.set.RegistrySet; import io.papermc.paper.registry.tag.TagKey; +import java.util.List; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.bukkit.inventory.ItemType; public abstract class CustomEnchantmentRegistry { - final public static String NAMESPACE = "vane_enchantments"; - final private static String TRANSLATE_KEY = "vane_enchantments.enchantment_%s.name"; + public static final String NAMESPACE = "vane_enchantments"; + private static final String TRANSLATE_KEY = "vane_enchantments.enchantment_%s.name"; Key key; Component description; int max_level; @@ -45,28 +43,28 @@ public CustomEnchantmentRegistry(String name, List> supported } /** - * Add exclusive enchantments to this enchantment: exclusive enchantments can't be on the - * same tool. + * Add exclusive enchantments to this enchantment: exclusive enchantments can't be on the same + * tool. */ - public CustomEnchantmentRegistry exclusive_with(List> enchantments){ + public CustomEnchantmentRegistry exclusive_with(List> enchantments) { this.exclusive_with = enchantments; return this; } /** - * Add exclusive enchantment tag to this enchantment: exclusive enchantments can't be on the - * same tool. + * Add exclusive enchantment tag to this enchantment: exclusive enchantments can't be on + * the same tool. */ - public CustomEnchantmentRegistry exclusive_with(TagKey enchantment_tag){ + public CustomEnchantmentRegistry exclusive_with(TagKey enchantment_tag) { this.exclusive_with_tags = enchantment_tag; return this; } - /** - * Get exclusive enchantments - */ - public RegistryKeySet exclusive_with(RegistryFreezeEvent freezeEvent) { - if(this.exclusive_with_tags != null) { + /** Get exclusive enchantments */ + public RegistryKeySet exclusive_with( + RegistryFreezeEvent freezeEvent + ) { + if (this.exclusive_with_tags != null) { return freezeEvent.getOrCreateTag(exclusive_with_tags); } else { return RegistrySet.keySet(RegistryKey.ENCHANTMENT, this.exclusive_with); @@ -78,22 +76,28 @@ public RegistryKeySet exclusive_with(RegistryFreezeEvent freezeEvent){ - freezeEvent.registry().register(TypedKey.create(RegistryKey.ENCHANTMENT, key), - e -> e.description(description) - .supportedItems(supported_items.size() > 0 ? RegistrySet.keySet(RegistryKey.ITEM, supported_items) : freezeEvent.getOrCreateTag(supported_item_tags)) - .anvilCost(1) - .maxLevel(max_level) - .weight(10) - .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 1)) - .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(3, 1)) - .activeSlots(EquipmentSlotGroup.ANY) - .exclusiveWith(this.exclusive_with(freezeEvent)) + public void register(RegistryFreezeEvent freezeEvent) { + freezeEvent + .registry() + .register(TypedKey.create(RegistryKey.ENCHANTMENT, key), e -> + e + .description(description) + .supportedItems( + supported_items.size() > 0 + ? RegistrySet.keySet(RegistryKey.ITEM, supported_items) + : freezeEvent.getOrCreateTag(supported_item_tags) + ) + .anvilCost(1) + .maxLevel(max_level) + .weight(10) + .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(1, 1)) + .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(3, 1)) + .activeSlots(EquipmentSlotGroup.ANY) + .exclusiveWith(this.exclusive_with(freezeEvent)) ); } public TypedKey typedKey(String name) { return TypedKey.create(RegistryKey.ENCHANTMENT, Key.key(NAMESPACE, name)); } - } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/Enchantments.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/Enchantments.java index 37fc9f86e..d97cc6eff 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/Enchantments.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/Enchantments.java @@ -5,19 +5,20 @@ @VaneModule(name = "enchantments", bstats = 8640, config_version = 1, lang_version = 4, storage_version = 1) public class Enchantments extends Module { - public Enchantments() { - new org.oddlama.vane.enchantments.items.Tomes(this); - new org.oddlama.vane.enchantments.enchantments.Angel(this); - new org.oddlama.vane.enchantments.enchantments.GrapplingHook(this); - new org.oddlama.vane.enchantments.enchantments.HellBent(this); - new org.oddlama.vane.enchantments.enchantments.Leafchopper(this); - new org.oddlama.vane.enchantments.enchantments.Lightning(this); - new org.oddlama.vane.enchantments.enchantments.Rake(this); - new org.oddlama.vane.enchantments.enchantments.Seeding(this); - new org.oddlama.vane.enchantments.enchantments.Soulbound(this); - new org.oddlama.vane.enchantments.enchantments.TakeOff(this); - new org.oddlama.vane.enchantments.enchantments.Unbreakable(this); - new org.oddlama.vane.enchantments.enchantments.Wings(this); - } + public Enchantments() { + new org.oddlama.vane.enchantments.items.Tomes(this); + + new org.oddlama.vane.enchantments.enchantments.Angel(this); + new org.oddlama.vane.enchantments.enchantments.GrapplingHook(this); + new org.oddlama.vane.enchantments.enchantments.HellBent(this); + new org.oddlama.vane.enchantments.enchantments.Leafchopper(this); + new org.oddlama.vane.enchantments.enchantments.Lightning(this); + new org.oddlama.vane.enchantments.enchantments.Rake(this); + new org.oddlama.vane.enchantments.enchantments.Seeding(this); + new org.oddlama.vane.enchantments.enchantments.Soulbound(this); + new org.oddlama.vane.enchantments.enchantments.TakeOff(this); + new org.oddlama.vane.enchantments.enchantments.Unbreakable(this); + new org.oddlama.vane.enchantments.enchantments.Wings(this); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/EnchantmentsBootstrapper.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/EnchantmentsBootstrapper.java index 9e6af1f3a..d3d5c5e87 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/EnchantmentsBootstrapper.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/EnchantmentsBootstrapper.java @@ -1,5 +1,8 @@ package org.oddlama.vane.enchantments; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import io.papermc.paper.registry.event.RegistryEvents; import org.jetbrains.annotations.NotNull; import org.oddlama.vane.enchantments.enchantments.registry.AngelRegistry; import org.oddlama.vane.enchantments.enchantments.registry.GrapplingHookRegistry; @@ -13,27 +16,27 @@ import org.oddlama.vane.enchantments.enchantments.registry.UnbreakableRegistry; import org.oddlama.vane.enchantments.enchantments.registry.WingsRegistry; -import io.papermc.paper.plugin.bootstrap.BootstrapContext; -import io.papermc.paper.plugin.bootstrap.PluginBootstrap; -import io.papermc.paper.registry.event.RegistryEvents; - public class EnchantmentsBootstrapper implements PluginBootstrap { @Override public void bootstrap(@NotNull BootstrapContext context) { - context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.freeze().newHandler(event -> { - new AngelRegistry(event); - new GrapplingHookRegistry(event); - new HellBentRegistry(event); - new LeafchopperRegistry(event); - new LightningRegistry(event); - new RakeRegistry(event); - new SeedingRegistry(event); - new WingsRegistry(event); - new SouldboundRegistry(event); - new TakeOffRegistry(event); - new UnbreakableRegistry(event); - })); + context + .getLifecycleManager() + .registerEventHandler( + RegistryEvents.ENCHANTMENT.freeze() + .newHandler(event -> { + new AngelRegistry(event); + new GrapplingHookRegistry(event); + new HellBentRegistry(event); + new LeafchopperRegistry(event); + new LightningRegistry(event); + new RakeRegistry(event); + new SeedingRegistry(event); + new WingsRegistry(event); + new SouldboundRegistry(event); + new TakeOffRegistry(event); + new UnbreakableRegistry(event); + }) + ); } - } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Angel.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Angel.java index 84c516e9c..6449ffdc0 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Angel.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Angel.java @@ -1,7 +1,6 @@ package org.oddlama.vane.enchantments.enchantments; import java.util.List; - import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.event.EventHandler; @@ -16,99 +15,101 @@ import org.oddlama.vane.core.config.loot.LootTableList; import org.oddlama.vane.core.config.recipes.RecipeList; import org.oddlama.vane.core.config.recipes.ShapedRecipeDefinition; -import org.oddlama.vane.core.module.Context; import org.oddlama.vane.core.enchantments.CustomEnchantment; +import org.oddlama.vane.core.module.Context; import org.oddlama.vane.enchantments.Enchantments; @VaneEnchantment(name = "angel", max_level = 5, rarity = Rarity.VERY_RARE, treasure = true, allow_custom = true) public class Angel extends CustomEnchantment { - @ConfigDouble( - def = 0.1, - min = 0.0, - max = 1.0, - desc = "Acceleration percentage. Each tick, the current flying speed is increased X percent towards the target speed. Low values (~0.1) typically result in a smooth acceleration curve and a natural feeling." - ) - private double config_acceleration_percentage; + @ConfigDouble( + def = 0.1, + min = 0.0, + max = 1.0, + desc = "Acceleration percentage. Each tick, the current flying speed is increased X percent towards the target speed. Low values (~0.1) typically result in a smooth acceleration curve and a natural feeling." + ) + private double config_acceleration_percentage; - @ConfigDoubleList( - def = { 0.7, 1.1, 1.4, 1.7, 2.0 }, - min = 0.0, - desc = "Flying speed in blocks per second for each enchantment level." - ) - private List config_speed; + @ConfigDoubleList( + def = { 0.7, 1.1, 1.4, 1.7, 2.0 }, + min = 0.0, + desc = "Flying speed in blocks per second for each enchantment level." + ) + private List config_speed; - public Angel(Context context) { - super(context); - } + public Angel(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("prp", "mbm", "mdm") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") - .set_ingredient('m', Material.PHANTOM_MEMBRANE) - .set_ingredient('d', Material.DRAGON_BREATH) - .set_ingredient('p', Material.PUFFERFISH_BUCKET) - .set_ingredient('r', Material.FIREWORK_ROCKET) - .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("prp", "mbm", "mdm") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") + .set_ingredient('m', Material.PHANTOM_MEMBRANE) + .set_ingredient('d', Material.DRAGON_BREATH) + .set_ingredient('p', Material.PUFFERFISH_BUCKET) + .set_ingredient('r', Material.FIREWORK_ROCKET) + .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } - @Override - public LootTableList default_loot_tables() { - return LootTableList.of(new LootDefinition("generic") - .in(LootTables.BURIED_TREASURE) - .in(LootTables.PILLAGER_OUTPOST) - .in(LootTables.RUINED_PORTAL) - .in(LootTables.STRONGHOLD_LIBRARY) - .in(LootTables.UNDERWATER_RUIN_BIG) - .in(LootTables.VILLAGE_TEMPLE) - .add(1.0 / 250, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.BURIED_TREASURE) + .in(LootTables.PILLAGER_OUTPOST) + .in(LootTables.RUINED_PORTAL) + .in(LootTables.STRONGHOLD_LIBRARY) + .in(LootTables.UNDERWATER_RUIN_BIG) + .in(LootTables.VILLAGE_TEMPLE) + .add(1.0 / 250, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } - private double get_speed(int level) { - if (level > 0 && level <= config_speed.size()) { - return config_speed.get(level - 1); - } - return config_speed.get(0); - } + private double get_speed(int level) { + if (level > 0 && level <= config_speed.size()) { + return config_speed.get(level - 1); + } + return config_speed.get(0); + } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_move(final PlayerMoveEvent event) { - // Check sneaking and flying - final var player = event.getPlayer(); - if (!player.isSneaking() || !player.isGliding()) { - return; - } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_move(final PlayerMoveEvent event) { + // Check sneaking and flying + final var player = event.getPlayer(); + if (!player.isSneaking() || !player.isGliding()) { + return; + } - // Check enchantment level - final var chest = player.getEquipment().getChestplate(); - if (chest == null) { // Can happen due to other plugins - return; - } - final var level = chest.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } + // Check enchantment level + final var chest = player.getEquipment().getChestplate(); + if (chest == null) { // Can happen due to other plugins + return; + } + final var level = chest.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } - final var loc = player.getLocation(); - final var dir = loc.getDirection(); - if (dir.length() == 0) { - return; - } + final var loc = player.getLocation(); + final var dir = loc.getDirection(); + if (dir.length() == 0) { + return; + } - // Scale the delta dependent on the angle. Higher angle -> less effect - final var vel = player.getVelocity(); - final var delta = config_acceleration_percentage * (1.0 - dir.angle(vel) / Math.PI); - final var factor = get_speed(level); + // Scale the delta dependent on the angle. Higher angle -> less effect + final var vel = player.getVelocity(); + final var delta = config_acceleration_percentage * (1.0 - dir.angle(vel) / Math.PI); + final var factor = get_speed(level); - // Exponential moving average between velocity and target velocity - final var new_vel = vel.multiply(1.0 - delta).add(dir.normalize().multiply(delta * factor)); - player.setVelocity(new_vel); + // Exponential moving average between velocity and target velocity + final var new_vel = vel.multiply(1.0 - delta).add(dir.normalize().multiply(delta * factor)); + player.setVelocity(new_vel); - // Spawn particles - loc - .getWorld() - .spawnParticle(Particle.FIREWORK, loc, 0, -new_vel.getX(), -new_vel.getY(), -new_vel.getZ(), 0.4); - } + // Spawn particles + loc.getWorld().spawnParticle(Particle.FIREWORK, loc, 0, -new_vel.getX(), -new_vel.getY(), -new_vel.getZ(), 0.4); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/GrapplingHook.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/GrapplingHook.java index d8457532c..51d7718be 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/GrapplingHook.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/GrapplingHook.java @@ -1,7 +1,6 @@ package org.oddlama.vane.enchantments.enchantments; import java.util.List; - import org.bukkit.Material; import org.bukkit.enchantments.EnchantmentTarget; import org.bukkit.event.EventHandler; @@ -14,104 +13,106 @@ import org.oddlama.vane.annotation.enchantment.VaneEnchantment; import org.oddlama.vane.core.config.recipes.RecipeList; import org.oddlama.vane.core.config.recipes.ShapedRecipeDefinition; -import org.oddlama.vane.core.module.Context; import org.oddlama.vane.core.enchantments.CustomEnchantment; +import org.oddlama.vane.core.module.Context; import org.oddlama.vane.enchantments.Enchantments; @VaneEnchantment( - name = "grappling_hook", - max_level = 3, - rarity = Rarity.UNCOMMON, - treasure = true, - target = EnchantmentTarget.FISHING_ROD + name = "grappling_hook", + max_level = 3, + rarity = Rarity.UNCOMMON, + treasure = true, + target = EnchantmentTarget.FISHING_ROD ) public class GrapplingHook extends CustomEnchantment { - // Constant offset to the added velocity, so the player will always move up a little. - private static final Vector CONSTANT_OFFSET = new Vector(0.0, 0.2, 0.0); + // Constant offset to the added velocity, so the player will always move up a little. + private static final Vector CONSTANT_OFFSET = new Vector(0.0, 0.2, 0.0); - @ConfigDouble( - def = 16.0, - min = 2.0, - max = 50.0, - desc = "Ideal grappling distance for maximum grapple strength. Strength increases rapidly before, and falls of slowly after." - ) - private double config_ideal_distance; + @ConfigDouble( + def = 16.0, + min = 2.0, + max = 50.0, + desc = "Ideal grappling distance for maximum grapple strength. Strength increases rapidly before, and falls of slowly after." + ) + private double config_ideal_distance; - @ConfigDoubleList(def = { 1.6, 2.1, 2.7 }, min = 0.0, desc = "Grappling strength for each enchantment level.") - private List config_strength; + @ConfigDoubleList(def = { 1.6, 2.1, 2.7 }, min = 0.0, desc = "Grappling strength for each enchantment level.") + private List config_strength; - public GrapplingHook(Context context) { - super(context); - } + public GrapplingHook(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("h", "l", "b") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('l', Material.LEAD) - .set_ingredient('h', Material.TRIPWIRE_HOOK) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("h", "l", "b") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('l', Material.LEAD) + .set_ingredient('h', Material.TRIPWIRE_HOOK) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } - private double get_strength(int level) { - if (level > 0 && level <= config_strength.size()) { - return config_strength.get(level - 1); - } - return config_strength.get(0); - } + private double get_strength(int level) { + if (level > 0 && level <= config_strength.size()) { + return config_strength.get(level - 1); + } + return config_strength.get(0); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_fish_event(final PlayerFishEvent event) { - // Get enchantment level - final var player = event.getPlayer(); - var item = player.getEquipment().getItemInMainHand(); - var level = item.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - item = player.getEquipment().getItemInOffHand(); - level = item.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_fish_event(final PlayerFishEvent event) { + // Get enchantment level + final var player = event.getPlayer(); + var item = player.getEquipment().getItemInMainHand(); + var level = item.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + item = player.getEquipment().getItemInOffHand(); + level = item.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } + } - // Grapple when stuck in the ground - switch (event.getState()) { - case FAILED_ATTEMPT: - // Assume stuck in ground if velocity is < 0.01 - if (event.getHook().getVelocity().length() >= 0.01) { - return; - } + // Grapple when stuck in the ground + switch (event.getState()) { + case FAILED_ATTEMPT: + // Assume stuck in ground if velocity is < 0.01 + if (event.getHook().getVelocity().length() >= 0.01) { + return; + } - // Block must be solid to be hooked - if (!event.getHook().getLocation().getBlock().getType().isSolid()) { - return; - } - break; - case IN_GROUND: - break; - default: - return; - } + // Block must be solid to be hooked + if (!event.getHook().getLocation().getBlock().getType().isSolid()) { + return; + } + break; + case IN_GROUND: + break; + default: + return; + } - var direction = event.getHook().getLocation().subtract(player.getLocation()).toVector(); - var distance = direction.length(); - var attenuation = distance / config_ideal_distance; + var direction = event.getHook().getLocation().subtract(player.getLocation()).toVector(); + var distance = direction.length(); + var attenuation = distance / config_ideal_distance; - // Reset fall distance - player.setFallDistance(0.0f); + // Reset fall distance + player.setFallDistance(0.0f); - // Set player velocity - player.setVelocity( - player - .getVelocity() - .add( - direction - .normalize() - .multiply(get_strength(level) * Math.exp(1.0 - attenuation) * attenuation) - .add(CONSTANT_OFFSET) - ) - ); - } + // Set player velocity + player.setVelocity( + player + .getVelocity() + .add( + direction + .normalize() + .multiply(get_strength(level) * Math.exp(1.0 - attenuation) * attenuation) + .add(CONSTANT_OFFSET) + ) + ); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/HellBent.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/HellBent.java index 29bb9cdbf..9bfc59f20 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/HellBent.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/HellBent.java @@ -13,56 +13,60 @@ import org.oddlama.vane.core.config.loot.LootTableList; import org.oddlama.vane.core.config.recipes.RecipeList; import org.oddlama.vane.core.config.recipes.ShapedRecipeDefinition; -import org.oddlama.vane.core.module.Context; import org.oddlama.vane.core.enchantments.CustomEnchantment; +import org.oddlama.vane.core.module.Context; import org.oddlama.vane.enchantments.Enchantments; @VaneEnchantment(name = "hell_bent", rarity = Rarity.COMMON, treasure = true, target = EnchantmentTarget.ARMOR_HEAD) public class HellBent extends CustomEnchantment { - public HellBent(Context context) { - super(context); - } + public HellBent(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("m", "b", "t") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('t', Material.TURTLE_HELMET) - .set_ingredient('m', Material.MUSIC_DISC_PIGSTEP) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("m", "b", "t") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('t', Material.TURTLE_HELMET) + .set_ingredient('m', Material.MUSIC_DISC_PIGSTEP) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } - @Override - public LootTableList default_loot_tables() { - return LootTableList.of(new LootDefinition("generic") - .in(LootTables.BASTION_BRIDGE) - .in(LootTables.BASTION_HOGLIN_STABLE) - .in(LootTables.BASTION_OTHER) - .in(LootTables.BASTION_TREASURE) - .add(1.0 / 50, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.BASTION_BRIDGE) + .in(LootTables.BASTION_HOGLIN_STABLE) + .in(LootTables.BASTION_OTHER) + .in(LootTables.BASTION_TREASURE) + .add(1.0 / 50, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_damage(final EntityDamageEvent event) { - final var entity = event.getEntity(); - if (!(entity instanceof Player) || event.getCause() != EntityDamageEvent.DamageCause.FLY_INTO_WALL) { - return; - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_damage(final EntityDamageEvent event) { + final var entity = event.getEntity(); + if (!(entity instanceof Player) || event.getCause() != EntityDamageEvent.DamageCause.FLY_INTO_WALL) { + return; + } - // Get helmet - final var player = (Player) entity; - final var helmet = player.getEquipment().getHelmet(); - if (helmet == null) { - return; - } + // Get helmet + final var player = (Player) entity; + final var helmet = player.getEquipment().getHelmet(); + if (helmet == null) { + return; + } - // Check enchantment - if (helmet.getEnchantmentLevel(this.bukkit()) == 0) { - return; - } + // Check enchantment + if (helmet.getEnchantmentLevel(this.bukkit()) == 0) { + return; + } - event.setCancelled(true); - } + event.setCancelled(true); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Leafchopper.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Leafchopper.java index ade1f6b6b..b844e53b3 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Leafchopper.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Leafchopper.java @@ -21,50 +21,52 @@ @VaneEnchantment(name = "leafchopper", rarity = Rarity.COMMON, treasure = true, target = EnchantmentTarget.TOOL) public class Leafchopper extends CustomEnchantment { - public Leafchopper(Context context) { - super(context); - } + public Leafchopper(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape(" s ", "sbs", " s ") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('s', Material.SHEARS) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape(" s ", "sbs", " s ") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('s', Material.SHEARS) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_left_click_leaves(PlayerInteractEvent event) { - if ( - !event.hasBlock() || event.getHand() != EquipmentSlot.HAND || event.getAction() != Action.LEFT_CLICK_BLOCK - ) { - return; - } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_left_click_leaves(PlayerInteractEvent event) { + if ( + !event.hasBlock() || event.getHand() != EquipmentSlot.HAND || event.getAction() != Action.LEFT_CLICK_BLOCK + ) { + return; + } - // Check leaves - var block = event.getClickedBlock(); - var data = block.getBlockData(); - if (!(data instanceof Leaves)) { - return; - } + // Check leaves + var block = event.getClickedBlock(); + var data = block.getBlockData(); + if (!(data instanceof Leaves)) { + return; + } - // Check non persistent leaves - var leaves = (Leaves) data; - if (leaves.isPersistent()) { - return; - } + // Check non persistent leaves + var leaves = (Leaves) data; + if (leaves.isPersistent()) { + return; + } - // Check enchantment level - final var player = event.getPlayer(); - final var item = player.getEquipment().getItemInMainHand(); - final var level = item.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } + // Check enchantment level + final var player = event.getPlayer(); + final var item = player.getEquipment().getItemInMainHand(); + final var level = item.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } - // Break instantly, for no additional durability cost. - block.breakNaturally(); - block.getWorld().playSound(block.getLocation(), Sound.BLOCK_GRASS_BREAK, SoundCategory.BLOCKS, 1.0f, 1.0f); - } + // Break instantly, for no additional durability cost. + block.breakNaturally(); + block.getWorld().playSound(block.getLocation(), Sound.BLOCK_GRASS_BREAK, SoundCategory.BLOCKS, 1.0f, 1.0f); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Lightning.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Lightning.java index bf866451b..efd495865 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Lightning.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Lightning.java @@ -19,10 +19,10 @@ import org.oddlama.vane.enchantments.Enchantments; @VaneEnchantment( - name = "lightning", - max_level = 1, - rarity = Rarity.RARE, - treasure = true, + name = "lightning", + max_level = 1, + rarity = Rarity.RARE, + treasure = true, target = EnchantmentTarget.WEAPON ) public class Lightning extends CustomEnchantment { @@ -30,56 +30,49 @@ public class Lightning extends CustomEnchantment { public Lightning(Context context) { super(context, false); } - + @ConfigBoolean( def = true, desc = "Toggle lightning enchantment to cancel lightning damage for wielders of the enchant" ) private boolean config_lightning_protection; - @ConfigInt( - def = 4, - min = 0, - max = 20, - desc = "Damage modifier for the lightning enchant" - ) + @ConfigInt(def = 4, min = 0, max = 20, desc = "Damage modifier for the lightning enchant") private int config_lightning_damage; - @ConfigBoolean( - def = true, - desc = "Enable lightning to work in rainstorms as well" - ) + @ConfigBoolean(def = true, desc = "Enable lightning to work in rainstorms as well") private boolean config_lightning_rain; @Override public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("r r","utu"," b ") - .set_ingredient('r', Material.LIGHTNING_ROD) - .set_ingredient('t', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('b', Material.BEACON) - .set_ingredient('u', Material.TOTEM_OF_UNDYING) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("r r", "utu", " b ") + .set_ingredient('r', Material.LIGHTNING_ROD) + .set_ingredient('t', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('b', Material.BEACON) + .set_ingredient('u', Material.TOTEM_OF_UNDYING) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void on_lightning_attack(final EntityDamageEvent event) { - // Check if an entity is a player - if(!(event.getEntity() instanceof Player)) return; + if (!(event.getEntity() instanceof Player)) return; // Check to see if they were struck by lightning - if(!(event.getCause() == DamageCause.LIGHTNING)) return; + if (!(event.getCause() == DamageCause.LIGHTNING)) return; // Check to see if lightning protection is off - if(!config_lightning_protection) return; + if (!config_lightning_protection) return; Player player = (Player) event.getEntity(); final var item = player.getEquipment().getItemInMainHand(); final var level = item.getEnchantmentLevel(this.bukkit()); - + // If they are not holding a lightning sword, they still take the damage - if(level == 0) return; + if (level == 0) return; // Cancel the damage to the event event.setCancelled(true); @@ -88,10 +81,10 @@ public void on_lightning_attack(final EntityDamageEvent event) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void on_sword_attack(final EntityDamageByEntityEvent event) { // Only strike when an entity is a player - if(!(event.getDamager() instanceof Player)) return; + if (!(event.getDamager() instanceof Player)) return; - //if not an attack with a weapon exit - if(event.getCause()!=DamageCause.ENTITY_ATTACK) return; + // if not an attack with a weapon exit + if (event.getCause() != DamageCause.ENTITY_ATTACK) return; Player damager = (Player) event.getDamager(); final var damagee = event.getEntity(); @@ -103,16 +96,16 @@ public void on_sword_attack(final EntityDamageByEntityEvent event) { if (level == 0) return; // Get Storm status - if(!world.hasStorm()) return; + if (!world.hasStorm()) return; // Exit if config set to thunder only - if(!config_lightning_rain && !world.isThundering()) return; + if (!config_lightning_rain && !world.isThundering()) return; // Test if sky is visible - if(damagee.getLocation().getBlockY() < world.getHighestBlockYAt(damagee.getLocation())) return; + if (damagee.getLocation().getBlockY() < world.getHighestBlockYAt(damagee.getLocation())) return; // Execute event.setDamage(event.getDamage() + config_lightning_damage); - world.strikeLightning(damagee.getLocation()); - } + world.strikeLightning(damagee.getLocation()); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Rake.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Rake.java index 89d48c583..af1ca39b8 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Rake.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Rake.java @@ -22,48 +22,50 @@ @VaneEnchantment(name = "rake", max_level = 4, rarity = Rarity.COMMON, treasure = true, target = EnchantmentTarget.TOOL) public class Rake extends CustomEnchantment { - public Rake(Context context) { - super(context); - } + public Rake(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape(" h ", "hbh", " h ") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('h', Material.GOLDEN_HOE) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape(" h ", "hbh", " h ") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('h', Material.GOLDEN_HOE) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_till_farmland(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_till_farmland(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } - // Only till additional blocks when right-clicking farmland - if (event.getClickedBlock().getType() != Material.FARMLAND) { - return; - } + // Only till additional blocks when right-clicking farmland + if (event.getClickedBlock().getType() != Material.FARMLAND) { + return; + } - // Get enchantment level - final var player = event.getPlayer(); - final var item = player.getEquipment().getItem(event.getHand()); - final var level = item.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } + // Get enchantment level + final var player = event.getPlayer(); + final var item = player.getEquipment().getItem(event.getHand()); + final var level = item.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } - // Get tillable block - final var tillable = next_tillable_block(event.getClickedBlock(), level, true); - if (tillable == null) { - return; - } + // Get tillable block + final var tillable = next_tillable_block(event.getClickedBlock(), level, true); + if (tillable == null) { + return; + } - // Till block - if (till_block(player, tillable)) { - damage_item(player, item, 1); - swing_arm(player, event.getHand()); - } - } + // Till block + if (till_block(player, tillable)) { + damage_item(player, item, 1); + swing_arm(player, event.getHand()); + } + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Seeding.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Seeding.java index 93f799f7c..fa67c6ee4 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Seeding.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Seeding.java @@ -23,65 +23,67 @@ import org.oddlama.vane.enchantments.Enchantments; @VaneEnchantment( - name = "seeding", - max_level = 4, - rarity = Rarity.COMMON, - treasure = true, - target = EnchantmentTarget.TOOL + name = "seeding", + max_level = 4, + rarity = Rarity.COMMON, + treasure = true, + target = EnchantmentTarget.TOOL ) public class Seeding extends CustomEnchantment { - public Seeding(Context context) { - super(context); - } + public Seeding(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("1 7", "2b6", "345") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('1', Material.PUMPKIN_SEEDS) - .set_ingredient('2', Material.CARROT) - .set_ingredient('3', Material.WHEAT_SEEDS) - .set_ingredient('4', Material.NETHER_WART) - .set_ingredient('5', Material.BEETROOT_SEEDS) - .set_ingredient('6', Material.POTATO) - .set_ingredient('7', Material.MELON_SEEDS) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("1 7", "2b6", "345") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('1', Material.PUMPKIN_SEEDS) + .set_ingredient('2', Material.CARROT) + .set_ingredient('3', Material.WHEAT_SEEDS) + .set_ingredient('4', Material.NETHER_WART) + .set_ingredient('5', Material.BEETROOT_SEEDS) + .set_ingredient('6', Material.POTATO) + .set_ingredient('7', Material.MELON_SEEDS) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_right_click_plant(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_right_click_plant(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } - // Only seed when right-clicking a plant - final var plant_type = event.getClickedBlock().getType(); - if (!is_seeded_plant(plant_type)) { - return; - } + // Only seed when right-clicking a plant + final var plant_type = event.getClickedBlock().getType(); + if (!is_seeded_plant(plant_type)) { + return; + } - // Get enchantment level - final var player = event.getPlayer(); - final var item = player.getEquipment().getItem(event.getHand()); - final var level = item.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } + // Get enchantment level + final var player = event.getPlayer(); + final var item = player.getEquipment().getItem(event.getHand()); + final var level = item.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } - // Get seedable block - final var seed_type = seed_for(plant_type); - final var farmland_type = farmland_for(seed_type); - final var seedable = next_seedable_block(event.getClickedBlock(), farmland_type, level); - if (seedable == null) { - return; - } + // Get seedable block + final var seed_type = seed_for(plant_type); + final var farmland_type = farmland_for(seed_type); + final var seedable = next_seedable_block(event.getClickedBlock(), farmland_type, level); + if (seedable == null) { + return; + } - // Seed block - if (seed_block(player, item, seedable, plant_type, seed_type)) { - damage_item(player, item, 1); - swing_arm(player, event.getHand()); - } - } + // Seed block + if (seed_block(player, item, seedable, plant_type, seed_type)) { + damage_item(player, item, 1); + swing_arm(player, event.getHand()); + } + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Soulbound.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Soulbound.java index 261b038f1..12a9f6c2b 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Soulbound.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Soulbound.java @@ -1,5 +1,7 @@ package org.oddlama.vane.enchantments.enchantments; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.event.Event; @@ -21,169 +23,170 @@ import org.oddlama.vane.core.config.recipes.RecipeList; import org.oddlama.vane.core.config.recipes.ShapedRecipeDefinition; import org.oddlama.vane.core.data.CooldownData; +import org.oddlama.vane.core.enchantments.CustomEnchantment; import org.oddlama.vane.core.lang.TranslatedMessage; import org.oddlama.vane.core.module.Context; -import org.oddlama.vane.core.enchantments.CustomEnchantment; import org.oddlama.vane.enchantments.Enchantments; import org.oddlama.vane.util.StorageUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; - @VaneEnchantment(name = "soulbound", rarity = Rarity.RARE, treasure = true, allow_custom = true) public class Soulbound extends CustomEnchantment { - @ConfigLong( - def = 2000, - min = 0, - desc = "Window to allow Soulbound item drop immediately after a previous drop in milliseconds" - ) - public long config_cooldown; - - private static final NamespacedKey IGNORE_SOULBOUND_DROP = StorageUtil.namespaced_key( - "vane_enchantments", - "ignore_soulbound_drop" - ); - private CooldownData drop_cooldown = new CooldownData(IGNORE_SOULBOUND_DROP, config_cooldown); - - @LangMessage - public TranslatedMessage lang_drop_lock_warning; - - @LangMessage - public TranslatedMessage lang_dropped_notification; - - @LangMessage - public TranslatedMessage lang_drop_cooldown; - - public Soulbound(Context context) { - super(context); - } - - @Override - public void on_config_change() { - super.on_config_change(); - drop_cooldown = new CooldownData(IGNORE_SOULBOUND_DROP, config_cooldown); - } - - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("cqc", "obe", "rgt") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") - .set_ingredient('c', Material.CHAIN) - .set_ingredient('q', Material.WRITABLE_BOOK) - .set_ingredient('o', Material.BONE) - .set_ingredient('r', "minecraft:enchanted_book#enchants{minecraft:binding_curse*1}") - .set_ingredient('g', Material.GHAST_TEAR) - .set_ingredient('t', Material.TOTEM_OF_UNDYING) - .set_ingredient('e', Material.ENDER_EYE) - .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } - - @Override - public LootTableList default_loot_tables() { - return LootTableList.of(new LootDefinition("generic") - .in(LootTables.BASTION_TREASURE) - .add(1.0 / 15, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } - - @Override - public Component apply_display_format(Component component) { - return component.color(NamedTextColor.DARK_GRAY); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_death(final PlayerDeathEvent event) { - final var keep_items = event.getItemsToKeep(); - - // Keep all soulbound items - final var it = event.getDrops().iterator(); - while (it.hasNext()) { - final var drop = it.next(); - if (is_soulbound(drop)) { - keep_items.add(drop); - it.remove(); - } - } - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void on_player_inventory_check(final InventoryClickEvent event) { - if (event.getCursor() == null) return; - if (!is_soulbound(event.getCursor())) return; - if ( - event.getAction() == InventoryAction.DROP_ALL_CURSOR || event.getAction() == InventoryAction.DROP_ONE_CURSOR - ) { - boolean too_slow = drop_cooldown.peek_cooldown(event.getCursor().getItemMeta()); - if (too_slow) { - // Dropped too slowly, refresh and cancel - final ItemMeta meta = event.getCursor().getItemMeta(); - drop_cooldown.check_or_update_cooldown(meta); - event.getCursor().setItemMeta(meta); - lang_drop_cooldown.send_action_bar(event.getWhoClicked()); - event.setResult(Event.Result.DENY); - return; - } - // else allow as normal - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_player_drop_item(final PlayerDropItemEvent event) { - // A player cannot drop soulbound items. - // Prevents yeeting your best sword out of existence. - // (It's okay to put them into chests.) - final var dropped_item = event.getItemDrop().getItemStack(); - if (is_soulbound(dropped_item)) { - boolean too_slow = drop_cooldown.peek_cooldown(dropped_item.getItemMeta()); - if (!too_slow) { - var meta = dropped_item.getItemMeta(); - drop_cooldown.clear(dropped_item.getItemMeta()); - dropped_item.setItemMeta(meta); - lang_dropped_notification.send(event.getPlayer(), dropped_item.displayName()); - return; - } - final var inventory = event.getPlayer().getInventory(); - if (inventory.firstEmpty() != -1) { - // We still have space in the inventory, so the player tried to drop it with Q. - event.setCancelled(true); - lang_drop_lock_warning.send_action_bar( - event.getPlayer(), - event.getItemDrop().getItemStack().displayName() - ); - } else { - // Inventory is full (e.g., when exiting crafting table with soulbound item in it) - // so we drop the first non-soulbound item (if any) instead. - final var it = inventory.iterator(); - ItemStack non_soulbound_item = null; - int non_soulbound_item_slot = 0; - while (it.hasNext()) { - final var item = it.next(); - if (item.getEnchantmentLevel(this.bukkit()) == 0) { - non_soulbound_item = item; - break; - } - - ++non_soulbound_item_slot; - } - - if (non_soulbound_item == null) { - // We can't prevent dropping a soulbound item. - // Well, that sucks. - return; - } - - // Drop the other item - final var player = event.getPlayer(); - inventory.setItem(non_soulbound_item_slot, dropped_item); - player.getLocation().getWorld().dropItem(player.getLocation(), non_soulbound_item); - lang_drop_lock_warning.send_action_bar(player, event.getItemDrop().getItemStack().displayName()); - event.setCancelled(true); - } - } - } - - private boolean is_soulbound(ItemStack dropped_item) { - return dropped_item.getEnchantmentLevel(this.bukkit()) > 0; - } + @ConfigLong( + def = 2000, + min = 0, + desc = "Window to allow Soulbound item drop immediately after a previous drop in milliseconds" + ) + public long config_cooldown; + + private static final NamespacedKey IGNORE_SOULBOUND_DROP = StorageUtil.namespaced_key( + "vane_enchantments", + "ignore_soulbound_drop" + ); + private CooldownData drop_cooldown = new CooldownData(IGNORE_SOULBOUND_DROP, config_cooldown); + + @LangMessage + public TranslatedMessage lang_drop_lock_warning; + + @LangMessage + public TranslatedMessage lang_dropped_notification; + + @LangMessage + public TranslatedMessage lang_drop_cooldown; + + public Soulbound(Context context) { + super(context); + } + + @Override + public void on_config_change() { + super.on_config_change(); + drop_cooldown = new CooldownData(IGNORE_SOULBOUND_DROP, config_cooldown); + } + + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("cqc", "obe", "rgt") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") + .set_ingredient('c', Material.CHAIN) + .set_ingredient('q', Material.WRITABLE_BOOK) + .set_ingredient('o', Material.BONE) + .set_ingredient('r', "minecraft:enchanted_book#enchants{minecraft:binding_curse*1}") + .set_ingredient('g', Material.GHAST_TEAR) + .set_ingredient('t', Material.TOTEM_OF_UNDYING) + .set_ingredient('e', Material.ENDER_EYE) + .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } + + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.BASTION_TREASURE) + .add(1.0 / 15, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } + + @Override + public Component apply_display_format(Component component) { + return component.color(NamedTextColor.DARK_GRAY); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_death(final PlayerDeathEvent event) { + final var keep_items = event.getItemsToKeep(); + + // Keep all soulbound items + final var it = event.getDrops().iterator(); + while (it.hasNext()) { + final var drop = it.next(); + if (is_soulbound(drop)) { + keep_items.add(drop); + it.remove(); + } + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on_player_inventory_check(final InventoryClickEvent event) { + if (event.getCursor() == null) return; + if (!is_soulbound(event.getCursor())) return; + if ( + event.getAction() == InventoryAction.DROP_ALL_CURSOR || event.getAction() == InventoryAction.DROP_ONE_CURSOR + ) { + boolean too_slow = drop_cooldown.peek_cooldown(event.getCursor().getItemMeta()); + if (too_slow) { + // Dropped too slowly, refresh and cancel + final ItemMeta meta = event.getCursor().getItemMeta(); + drop_cooldown.check_or_update_cooldown(meta); + event.getCursor().setItemMeta(meta); + lang_drop_cooldown.send_action_bar(event.getWhoClicked()); + event.setResult(Event.Result.DENY); + return; + } + // else allow as normal + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_player_drop_item(final PlayerDropItemEvent event) { + // A player cannot drop soulbound items. + // Prevents yeeting your best sword out of existence. + // (It's okay to put them into chests.) + final var dropped_item = event.getItemDrop().getItemStack(); + if (is_soulbound(dropped_item)) { + boolean too_slow = drop_cooldown.peek_cooldown(dropped_item.getItemMeta()); + if (!too_slow) { + var meta = dropped_item.getItemMeta(); + drop_cooldown.clear(dropped_item.getItemMeta()); + dropped_item.setItemMeta(meta); + lang_dropped_notification.send(event.getPlayer(), dropped_item.displayName()); + return; + } + final var inventory = event.getPlayer().getInventory(); + if (inventory.firstEmpty() != -1) { + // We still have space in the inventory, so the player tried to drop it with Q. + event.setCancelled(true); + lang_drop_lock_warning.send_action_bar( + event.getPlayer(), + event.getItemDrop().getItemStack().displayName() + ); + } else { + // Inventory is full (e.g., when exiting crafting table with soulbound item in it) + // so we drop the first non-soulbound item (if any) instead. + final var it = inventory.iterator(); + ItemStack non_soulbound_item = null; + int non_soulbound_item_slot = 0; + while (it.hasNext()) { + final var item = it.next(); + if (item.getEnchantmentLevel(this.bukkit()) == 0) { + non_soulbound_item = item; + break; + } + + ++non_soulbound_item_slot; + } + + if (non_soulbound_item == null) { + // We can't prevent dropping a soulbound item. + // Well, that sucks. + return; + } + + // Drop the other item + final var player = event.getPlayer(); + inventory.setItem(non_soulbound_item_slot, dropped_item); + player.getLocation().getWorld().dropItem(player.getLocation(), non_soulbound_item); + lang_drop_lock_warning.send_action_bar(player, event.getItemDrop().getItemStack().displayName()); + event.setCancelled(true); + } + } + } + + private boolean is_soulbound(ItemStack dropped_item) { + return dropped_item.getEnchantmentLevel(this.bukkit()) > 0; + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/TakeOff.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/TakeOff.java index 9550a6ee0..8731d06e2 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/TakeOff.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/TakeOff.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.concurrent.ThreadLocalRandom; - import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.entity.Player; @@ -28,86 +27,90 @@ @VaneEnchantment(name = "take_off", max_level = 3, rarity = Rarity.UNCOMMON, treasure = true, allow_custom = true) public class TakeOff extends CustomEnchantment { - @ConfigDoubleList(def = { 0.2, 0.4, 0.6 }, min = 0.0, desc = "Boost strength for each enchantment level.") - private List config_boost_strengths; + @ConfigDoubleList(def = { 0.2, 0.4, 0.6 }, min = 0.0, desc = "Boost strength for each enchantment level.") + private List config_boost_strengths; - public TakeOff(Context context) { - super(context); - } + public TakeOff(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("mbm", "psp") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") - .set_ingredient('m', Material.PHANTOM_MEMBRANE) - .set_ingredient('p', Material.PISTON) - .set_ingredient('s', Material.SLIME_BLOCK) - .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("mbm", "psp") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") + .set_ingredient('m', Material.PHANTOM_MEMBRANE) + .set_ingredient('p', Material.PISTON) + .set_ingredient('s', Material.SLIME_BLOCK) + .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } - @Override - public LootTableList default_loot_tables() { - return LootTableList.of(new LootDefinition("generic") - .in(LootTables.BURIED_TREASURE) - .in(LootTables.PILLAGER_OUTPOST) - .in(LootTables.RUINED_PORTAL) - .in(LootTables.SHIPWRECK_TREASURE) - .in(LootTables.STRONGHOLD_LIBRARY) - .in(LootTables.UNDERWATER_RUIN_BIG) - .in(LootTables.UNDERWATER_RUIN_SMALL) - .in(LootTables.VILLAGE_TEMPLE) - .in(LootTables.WOODLAND_MANSION) - .add(1.0 / 150, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.BURIED_TREASURE) + .in(LootTables.PILLAGER_OUTPOST) + .in(LootTables.RUINED_PORTAL) + .in(LootTables.SHIPWRECK_TREASURE) + .in(LootTables.STRONGHOLD_LIBRARY) + .in(LootTables.UNDERWATER_RUIN_BIG) + .in(LootTables.UNDERWATER_RUIN_SMALL) + .in(LootTables.VILLAGE_TEMPLE) + .in(LootTables.WOODLAND_MANSION) + .add(1.0 / 150, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } - private double get_boost_strength(int level) { - if (level > 0 && level <= config_boost_strengths.size()) { - return config_boost_strengths.get(level - 1); - } - return config_boost_strengths.get(0); - } + private double get_boost_strength(int level) { + if (level > 0 && level <= config_boost_strengths.size()) { + return config_boost_strengths.get(level - 1); + } + return config_boost_strengths.get(0); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_toggle_glide(EntityToggleGlideEvent event) { - if (!(event.getEntity() instanceof Player) || !event.isGliding()) { - return; - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_toggle_glide(EntityToggleGlideEvent event) { + if (!(event.getEntity() instanceof Player) || !event.isGliding()) { + return; + } - // Don't apply for sneaking players - final var player = (Player) event.getEntity(); - if (player.isSneaking()) { - return; - } + // Don't apply for sneaking players + final var player = (Player) event.getEntity(); + if (player.isSneaking()) { + return; + } - // Check enchantment level - final var chest = player.getEquipment().getChestplate(); - final var level = chest.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } + // Check enchantment level + final var chest = player.getEquipment().getChestplate(); + final var level = chest.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } - // Apply boost - apply_elytra_boost(player, get_boost_strength(level)); - damage_item(player, chest, (int) (1.0 + 2.0 * Math.random())); + // Apply boost + apply_elytra_boost(player, get_boost_strength(level)); + damage_item(player, chest, (int) (1.0 + 2.0 * Math.random())); - // Spawn particles - final var loc = player.getLocation(); - final var vel = player.getVelocity().length(); - for (int i = 0; i < 16; ++i) { - final var rnd = Vector.getRandom().subtract(new Vector(.5, .5, .5)).normalize().multiply(.25); - final var dir = rnd.clone().multiply(.5).subtract(player.getVelocity()); - loc - .getWorld() - .spawnParticle( - Particle.FIREWORK, - loc.add(rnd), - 0, - dir.getX(), - dir.getY(), - dir.getZ(), - vel * ThreadLocalRandom.current().nextDouble(0.4, 0.6) - ); - } - } + // Spawn particles + final var loc = player.getLocation(); + final var vel = player.getVelocity().length(); + for (int i = 0; i < 16; ++i) { + final var rnd = Vector.getRandom().subtract(new Vector(.5, .5, .5)).normalize().multiply(.25); + final var dir = rnd.clone().multiply(.5).subtract(player.getVelocity()); + loc + .getWorld() + .spawnParticle( + Particle.FIREWORK, + loc.add(rnd), + 0, + dir.getX(), + dir.getY(), + dir.getZ(), + vel * ThreadLocalRandom.current().nextDouble(0.4, 0.6) + ); + } + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Unbreakable.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Unbreakable.java index 7ff542440..e3c661466 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Unbreakable.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Unbreakable.java @@ -19,52 +19,54 @@ @VaneEnchantment(name = "unbreakable", rarity = Rarity.RARE, treasure = true, allow_custom = true) public class Unbreakable extends CustomEnchantment { - public Unbreakable(Context context) { - super(context); - } + public Unbreakable(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("waw", "nbn", "tst") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") - .set_ingredient('w', Material.WITHER_ROSE) - .set_ingredient('a', Material.ENCHANTED_GOLDEN_APPLE) - .set_ingredient('n', Material.NETHERITE_INGOT) - .set_ingredient('t', Material.TOTEM_OF_UNDYING) - .set_ingredient('s', Material.NETHER_STAR) - .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods"))); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("waw", "nbn", "tst") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_the_gods") + .set_ingredient('w', Material.WITHER_ROSE) + .set_ingredient('a', Material.ENCHANTED_GOLDEN_APPLE) + .set_ingredient('n', Material.NETHERITE_INGOT) + .set_ingredient('t', Material.TOTEM_OF_UNDYING) + .set_ingredient('s', Material.NETHER_STAR) + .result(on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } - @Override - public LootTableList default_loot_tables() { - return LootTableList.of( - new LootDefinition("generic") - .in(LootTables.ABANDONED_MINESHAFT) - .add(1.0 / 120, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")), - new LootDefinition("bastion") - .in(LootTables.BASTION_TREASURE) - .add(1.0 / 30, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) - ); - } + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.ABANDONED_MINESHAFT) + .add(1.0 / 120, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")), + new LootDefinition("bastion") + .in(LootTables.BASTION_TREASURE) + .add(1.0 / 30, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_the_gods")) + ); + } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) - public void on_player_item_damage(final PlayerItemDamageEvent event) { - // Check enchantment - final var item = event.getItem(); - if (item.getEnchantmentLevel(this.bukkit()) == 0) { - return; - } + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) + public void on_player_item_damage(final PlayerItemDamageEvent event) { + // Check enchantment + final var item = event.getItem(); + if (item.getEnchantmentLevel(this.bukkit()) == 0) { + return; + } - // Set item unbreakable to prevent further event calls - final var meta = item.getItemMeta(); - meta.setUnbreakable(true); - // Also hide the internal unbreakable tag on the client - meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); - item.setItemMeta(meta); + // Set item unbreakable to prevent further event calls + final var meta = item.getItemMeta(); + meta.setUnbreakable(true); + // Also hide the internal unbreakable tag on the client + meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); + item.setItemMeta(meta); - // Prevent damage - event.setDamage(0); - event.setCancelled(true); - } + // Prevent damage + event.setDamage(0); + event.setCancelled(true); + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Wings.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Wings.java index ff62d26fd..6ff7374b7 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Wings.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/Wings.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.concurrent.ThreadLocalRandom; - import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.event.EventHandler; @@ -29,108 +28,110 @@ @VaneEnchantment(name = "wings", max_level = 4, rarity = Rarity.RARE, treasure = true, allow_custom = true) public class Wings extends CustomEnchantment { - @ConfigIntList( - def = { 7000, 5000, 3500, 2800 }, - min = 0, - desc = "Boost cooldown in milliseconds for each enchantment level." - ) - private List config_boost_cooldowns; - - @ConfigDoubleList(def = { 0.4, 0.47, 0.54, 0.6 }, min = 0.0, desc = "Boost strength for each enchantment level.") - private List config_boost_strengths; - - public Wings(Context context) { - super(context); - } - - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape("m m", "dbd", "r r") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('m', Material.PHANTOM_MEMBRANE) - .set_ingredient('d', Material.DISPENSER) - .set_ingredient('r', Material.FIREWORK_ROCKET) - .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge"))); - } - - @Override - public LootTableList default_loot_tables() { - return LootTableList.of( - new LootDefinition("generic") - .in(LootTables.BURIED_TREASURE) - .in(LootTables.PILLAGER_OUTPOST) - .in(LootTables.RUINED_PORTAL) - .in(LootTables.SHIPWRECK_TREASURE) - .in(LootTables.STRONGHOLD_LIBRARY) - .in(LootTables.UNDERWATER_RUIN_BIG) - .in(LootTables.UNDERWATER_RUIN_SMALL) - .in(LootTables.VILLAGE_TEMPLE) - .in(LootTables.WOODLAND_MANSION) - .add(1.0 / 110, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_knowledge")), - new LootDefinition("bastion") - .in(LootTables.BASTION_TREASURE) - .add(1.0 / 10, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) - ); - } - - private int get_boost_cooldown(int level) { - if (level > 0 && level <= config_boost_cooldowns.size()) { - return config_boost_cooldowns.get(level - 1); - } - return config_boost_cooldowns.get(0); - } - - private double get_boost_strength(int level) { - if (level > 0 && level <= config_boost_strengths.size()) { - return config_boost_strengths.get(level - 1); - } - return config_boost_strengths.get(0); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_toggle_sneak(PlayerToggleSneakEvent event) { - // Check sneaking and flying - final var player = event.getPlayer(); - if (!event.isSneaking() || !player.isGliding()) { - return; - } - - // Check enchantment level - final var chest = player.getEquipment().getChestplate(); - final var level = chest.getEnchantmentLevel(this.bukkit()); - if (level == 0) { - return; - } - - // Check cooldown - if (player.getCooldown(Material.ELYTRA) > 0) { - return; - } - - // Apply boost - final var cooldown = ms_to_ticks(get_boost_cooldown(level)); - player.setCooldown(Material.ELYTRA, (int) cooldown); - apply_elytra_boost(player, get_boost_strength(level)); - damage_item(player, chest, (int) (1.0 + 2.0 * Math.random())); - - // Spawn particles - final var loc = player.getLocation(); - final var vel = player.getVelocity().length(); - for (int i = 0; i < 16; ++i) { - final var rnd = Vector.getRandom().subtract(new Vector(.5, .5, .5)).normalize().multiply(.25); - final var dir = rnd.clone().multiply(.5).subtract(player.getVelocity()); - loc - .getWorld() - .spawnParticle( - Particle.FIREWORK, - loc.add(rnd), - 0, - dir.getX(), - dir.getY(), - dir.getZ(), - vel * ThreadLocalRandom.current().nextDouble(0.4, 0.6) - ); - } - } + @ConfigIntList( + def = { 7000, 5000, 3500, 2800 }, + min = 0, + desc = "Boost cooldown in milliseconds for each enchantment level." + ) + private List config_boost_cooldowns; + + @ConfigDoubleList(def = { 0.4, 0.47, 0.54, 0.6 }, min = 0.0, desc = "Boost strength for each enchantment level.") + private List config_boost_strengths; + + public Wings(Context context) { + super(context); + } + + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape("m m", "dbd", "r r") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('m', Material.PHANTOM_MEMBRANE) + .set_ingredient('d', Material.DISPENSER) + .set_ingredient('r', Material.FIREWORK_ROCKET) + .result(on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } + + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.BURIED_TREASURE) + .in(LootTables.PILLAGER_OUTPOST) + .in(LootTables.RUINED_PORTAL) + .in(LootTables.SHIPWRECK_TREASURE) + .in(LootTables.STRONGHOLD_LIBRARY) + .in(LootTables.UNDERWATER_RUIN_BIG) + .in(LootTables.UNDERWATER_RUIN_SMALL) + .in(LootTables.VILLAGE_TEMPLE) + .in(LootTables.WOODLAND_MANSION) + .add(1.0 / 110, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_knowledge")), + new LootDefinition("bastion") + .in(LootTables.BASTION_TREASURE) + .add(1.0 / 10, 1, 1, on("vane_enchantments:enchanted_ancient_tome_of_knowledge")) + ); + } + + private int get_boost_cooldown(int level) { + if (level > 0 && level <= config_boost_cooldowns.size()) { + return config_boost_cooldowns.get(level - 1); + } + return config_boost_cooldowns.get(0); + } + + private double get_boost_strength(int level) { + if (level > 0 && level <= config_boost_strengths.size()) { + return config_boost_strengths.get(level - 1); + } + return config_boost_strengths.get(0); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_toggle_sneak(PlayerToggleSneakEvent event) { + // Check sneaking and flying + final var player = event.getPlayer(); + if (!event.isSneaking() || !player.isGliding()) { + return; + } + + // Check enchantment level + final var chest = player.getEquipment().getChestplate(); + final var level = chest.getEnchantmentLevel(this.bukkit()); + if (level == 0) { + return; + } + + // Check cooldown + if (player.getCooldown(Material.ELYTRA) > 0) { + return; + } + + // Apply boost + final var cooldown = ms_to_ticks(get_boost_cooldown(level)); + player.setCooldown(Material.ELYTRA, (int) cooldown); + apply_elytra_boost(player, get_boost_strength(level)); + damage_item(player, chest, (int) (1.0 + 2.0 * Math.random())); + + // Spawn particles + final var loc = player.getLocation(); + final var vel = player.getVelocity().length(); + for (int i = 0; i < 16; ++i) { + final var rnd = Vector.getRandom().subtract(new Vector(.5, .5, .5)).normalize().multiply(.25); + final var dir = rnd.clone().multiply(.5).subtract(player.getVelocity()); + loc + .getWorld() + .spawnParticle( + Particle.FIREWORK, + loc.add(rnd), + 0, + dir.getX(), + dir.getY(), + dir.getZ(), + vel * ThreadLocalRandom.current().nextDouble(0.4, 0.6) + ); + } + } } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/AngelRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/AngelRegistry.java index 16f30bf28..2b5f06551 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/AngelRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/AngelRegistry.java @@ -1,23 +1,21 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import java.util.List; - -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.ItemTypeKeys; +import java.util.List; import net.kyori.adventure.key.Key; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class AngelRegistry extends CustomEnchantmentRegistry { public AngelRegistry(RegistryFreezeEvent freezeEvent) { super("angel", List.of(ItemTypeKeys.ELYTRA), 5); - this.exclusive_with(List.of(TypedKey.create(RegistryKey.ENCHANTMENT, Key.key(NAMESPACE, "wings")))) - .register(freezeEvent); + this.exclusive_with(List.of(TypedKey.create(RegistryKey.ENCHANTMENT, Key.key(NAMESPACE, "wings")))).register( + freezeEvent + ); } - } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/GrapplingHookRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/GrapplingHookRegistry.java index 6bc773a52..d645c76c7 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/GrapplingHookRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/GrapplingHookRegistry.java @@ -1,11 +1,10 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class GrapplingHookRegistry extends CustomEnchantmentRegistry { @@ -13,5 +12,4 @@ public GrapplingHookRegistry(RegistryFreezeEvent freezeEvent) { super("hell_bent", ItemTypeTagKeys.ENCHANTABLE_HEAD_ARMOR, 1); this.register(freezeEvent); } - } diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LeafchopperRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LeafchopperRegistry.java index c8554c693..c07e75fd0 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LeafchopperRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LeafchopperRegistry.java @@ -1,13 +1,13 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; + +public class LeafchopperRegistry extends CustomEnchantmentRegistry { -public class LeafchopperRegistry extends CustomEnchantmentRegistry{ public LeafchopperRegistry(RegistryFreezeEvent freezeEvent) { super("leafchopper", ItemTypeTagKeys.AXES, 1); this.register(freezeEvent); diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LightningRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LightningRegistry.java index be35d6ad1..fbf9096f3 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LightningRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/LightningRegistry.java @@ -1,13 +1,13 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class LightningRegistry extends CustomEnchantmentRegistry { + public LightningRegistry(RegistryFreezeEvent freezeEvent) { super("lightning", ItemTypeTagKeys.ENCHANTABLE_SWORD, 1); this.register(freezeEvent); diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/RakeRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/RakeRegistry.java index ed213f717..8bd1595ba 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/RakeRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/RakeRegistry.java @@ -1,19 +1,15 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class RakeRegistry extends CustomEnchantmentRegistry { public RakeRegistry(RegistryFreezeEvent freezeEvent) { - super("rake", - ItemTypeTagKeys.HOES, - 4); + super("rake", ItemTypeTagKeys.HOES, 4); this.register(freezeEvent); } - -} \ No newline at end of file +} diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SeedingRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SeedingRegistry.java index 43b5eaa6b..1aba9cae5 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SeedingRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SeedingRegistry.java @@ -1,13 +1,13 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class SeedingRegistry extends CustomEnchantmentRegistry { + public SeedingRegistry(RegistryFreezeEvent freezeEvent) { super("seeding", ItemTypeTagKeys.HOES, 4); this.register(freezeEvent); diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SouldboundRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SouldboundRegistry.java index 3305f8874..c064a5e40 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SouldboundRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/SouldboundRegistry.java @@ -1,13 +1,13 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class SouldboundRegistry extends CustomEnchantmentRegistry { + public SouldboundRegistry(RegistryFreezeEvent freezeEvent) { super("soulbound", ItemTypeTagKeys.ENCHANTABLE_DURABILITY, 1); this.register(freezeEvent); diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/TakeOffRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/TakeOffRegistry.java index 9699b730a..6aab36f0c 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/TakeOffRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/TakeOffRegistry.java @@ -1,15 +1,14 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import java.util.List; - -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.ItemTypeKeys; +import java.util.List; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class TakeOffRegistry extends CustomEnchantmentRegistry { + public TakeOffRegistry(RegistryFreezeEvent freezeEvent) { super("take_off", List.of(ItemTypeKeys.ELYTRA), 3); this.register(freezeEvent); diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/UnbreakableRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/UnbreakableRegistry.java index dafadc4f3..c345693fc 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/UnbreakableRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/UnbreakableRegistry.java @@ -1,17 +1,15 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import java.util.List; - -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.EnchantmentKeys; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; +import java.util.List; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class UnbreakableRegistry extends CustomEnchantmentRegistry { - + public UnbreakableRegistry(RegistryFreezeEvent freezeEvent) { super("unbreakable", ItemTypeTagKeys.ENCHANTABLE_DURABILITY, 1); this.exclusive_with(List.of(EnchantmentKeys.UNBREAKING, EnchantmentKeys.MENDING)); diff --git a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/WingsRegistry.java b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/WingsRegistry.java index 984a37747..747330373 100644 --- a/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/WingsRegistry.java +++ b/vane-enchantments/src/main/java/org/oddlama/vane/enchantments/enchantments/registry/WingsRegistry.java @@ -1,13 +1,11 @@ package org.oddlama.vane.enchantments.enchantments.registry; -import java.util.List; - -import org.bukkit.enchantments.Enchantment; -import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; - import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.event.RegistryFreezeEvent; import io.papermc.paper.registry.keys.ItemTypeKeys; +import java.util.List; +import org.bukkit.enchantments.Enchantment; +import org.oddlama.vane.enchantments.CustomEnchantmentRegistry; public class WingsRegistry extends CustomEnchantmentRegistry { @@ -16,5 +14,4 @@ public WingsRegistry(RegistryFreezeEvent { - public Tomes(Context context) { - super(context, "tomes", - "These tomes are needed to craft custom enchantments. If you disable them here, you will need to adjust the recipes for the enchantments accordingly."); - new GrindstoneListener(get_context()); - new AncientTome(get_context()); - new EnchantedAncientTome(get_context()); - new AncientTomeOfKnowledge(get_context()); - new EnchantedAncientTomeOfKnowledge(get_context()); - new AncientTomeOfTheGods(get_context()); - new EnchantedAncientTomeOfTheGods(get_context()); - } - @VaneItem(name = "ancient_tome", base = Material.BOOK, model_data = 0x770000, version = 1) - public static class AncientTome extends CustomItem { - public AncientTome(Context context) { - super(context); - } + public Tomes(Context context) { + super( + context, + "tomes", + "These tomes are needed to craft custom enchantments. If you disable them here, you will need to adjust the recipes for the enchantments accordingly." + ); + new GrindstoneListener(get_context()); + new AncientTome(get_context()); + new EnchantedAncientTome(get_context()); + new AncientTomeOfKnowledge(get_context()); + new EnchantedAncientTomeOfKnowledge(get_context()); + new AncientTomeOfTheGods(get_context()); + new EnchantedAncientTomeOfTheGods(get_context()); + } - @Override - public LootTableList default_loot_tables() { - return LootTableList.of( - new LootDefinition("generic") - .in(LootTables.ABANDONED_MINESHAFT) - .in(LootTables.BASTION_BRIDGE) - .in(LootTables.BASTION_HOGLIN_STABLE) - .in(LootTables.BASTION_OTHER) - .in(LootTables.BASTION_TREASURE) - .in(LootTables.BURIED_TREASURE) - .in(LootTables.DESERT_PYRAMID) - .in(LootTables.END_CITY_TREASURE) - .in(LootTables.FISHING_TREASURE) - .in(LootTables.IGLOO_CHEST) - .in(LootTables.JUNGLE_TEMPLE) - .in(LootTables.NETHER_BRIDGE) - .in(LootTables.PILLAGER_OUTPOST) - .in(LootTables.RUINED_PORTAL) - .in(LootTables.SHIPWRECK_TREASURE) - .in(LootTables.STRONGHOLD_LIBRARY) - .in(LootTables.UNDERWATER_RUIN_BIG) - .in(LootTables.UNDERWATER_RUIN_SMALL) - .in(LootTables.VILLAGE_TEMPLE) - .in(LootTables.WOODLAND_MANSION) - .add(1.0 / 5, 0, 2, key().toString()), - new LootDefinition("ancientcity") - .in(LootTables.ANCIENT_CITY) - .add(1.0 / 20, 0, 2, key().toString()), - new LootDefinition("terralith_generic") - // terralith low - .in(StorageUtil.namespaced_key("terralith", "spire/common")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/generic_low")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/generic_low")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith/novice")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/smith/novice")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/tavern_downstairs")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/tavern_upstairs")) - // terralith normal - .in(StorageUtil.namespaced_key("terralith", "ruin/glacial/main_cs")) - .in(StorageUtil.namespaced_key("terralith", "spire/treasure")) - .in(StorageUtil.namespaced_key("terralith", "underground/chest")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/archer")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/attic")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/butcher")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/cartographer")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/generic")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/library")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/mason")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/smith")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/treasure")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/archer")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/attic")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/butcher")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/cartographer")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/fisherman")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/food")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/generic")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/library")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/mason")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/treasure")) - .in(StorageUtil.namespaced_key("terralith", "village/treasure/diamond")) - .in(StorageUtil.namespaced_key("terralith", "village/treasure/emerald")) - .in(StorageUtil.namespaced_key("terralith", "village/treasure/golem")) - .add(1.0 / 5, 0, 2, key().toString()), - new LootDefinition("terralith_rare") - // terralith rare - .in(StorageUtil.namespaced_key("terralith", "village/desert/smith/expert")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith/expert")) - .in(StorageUtil.namespaced_key("terralith", "spire/rare")) - .add(1.0 / 20, 0, 2, key().toString())); - } - } + @VaneItem(name = "ancient_tome", base = Material.BOOK, model_data = 0x770000, version = 1) + public static class AncientTome extends CustomItem { - @VaneItem(name = "enchanted_ancient_tome", base = Material.ENCHANTED_BOOK, model_data = 0x770001, version = 1) - public static class EnchantedAncientTome extends CustomItem { - public EnchantedAncientTome(Context context) { - super(context); - } - } + public AncientTome(Context context) { + super(context); + } - @VaneItem(name = "ancient_tome_of_knowledge", base = Material.BOOK, model_data = 0x770002, version = 1) - public static class AncientTomeOfKnowledge extends CustomItem { - public AncientTomeOfKnowledge(Context context) { - super(context); - } + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.ABANDONED_MINESHAFT) + .in(LootTables.BASTION_BRIDGE) + .in(LootTables.BASTION_HOGLIN_STABLE) + .in(LootTables.BASTION_OTHER) + .in(LootTables.BASTION_TREASURE) + .in(LootTables.BURIED_TREASURE) + .in(LootTables.DESERT_PYRAMID) + .in(LootTables.END_CITY_TREASURE) + .in(LootTables.FISHING_TREASURE) + .in(LootTables.IGLOO_CHEST) + .in(LootTables.JUNGLE_TEMPLE) + .in(LootTables.NETHER_BRIDGE) + .in(LootTables.PILLAGER_OUTPOST) + .in(LootTables.RUINED_PORTAL) + .in(LootTables.SHIPWRECK_TREASURE) + .in(LootTables.STRONGHOLD_LIBRARY) + .in(LootTables.UNDERWATER_RUIN_BIG) + .in(LootTables.UNDERWATER_RUIN_SMALL) + .in(LootTables.VILLAGE_TEMPLE) + .in(LootTables.WOODLAND_MANSION) + .add(1.0 / 5, 0, 2, key().toString()), + new LootDefinition("ancientcity").in(LootTables.ANCIENT_CITY).add(1.0 / 20, 0, 2, key().toString()), + new LootDefinition("terralith_generic") + // terralith low + .in(StorageUtil.namespaced_key("terralith", "spire/common")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/generic_low")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/generic_low")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith/novice")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/smith/novice")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/tavern_downstairs")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/tavern_upstairs")) + // terralith normal + .in(StorageUtil.namespaced_key("terralith", "ruin/glacial/main_cs")) + .in(StorageUtil.namespaced_key("terralith", "spire/treasure")) + .in(StorageUtil.namespaced_key("terralith", "underground/chest")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/archer")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/attic")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/butcher")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/cartographer")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/generic")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/library")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/mason")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/smith")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/treasure")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/archer")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/attic")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/butcher")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/cartographer")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/fisherman")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/food")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/generic")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/library")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/mason")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/treasure")) + .in(StorageUtil.namespaced_key("terralith", "village/treasure/diamond")) + .in(StorageUtil.namespaced_key("terralith", "village/treasure/emerald")) + .in(StorageUtil.namespaced_key("terralith", "village/treasure/golem")) + .add(1.0 / 5, 0, 2, key().toString()), + new LootDefinition("terralith_rare") + // terralith rare + .in(StorageUtil.namespaced_key("terralith", "village/desert/smith/expert")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith/expert")) + .in(StorageUtil.namespaced_key("terralith", "spire/rare")) + .add(1.0 / 20, 0, 2, key().toString()) + ); + } + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapelessRecipeDefinition("generic") - .add_ingredient("vane_enchantments:ancient_tome") - .add_ingredient(Material.FEATHER) - .add_ingredient(Material.BLAZE_ROD) - .add_ingredient(Material.GHAST_TEAR) - .result(key().toString())); - } + @VaneItem(name = "enchanted_ancient_tome", base = Material.ENCHANTED_BOOK, model_data = 0x770001, version = 1) + public static class EnchantedAncientTome extends CustomItem { - @Override - public LootTableList default_loot_tables() { - return LootTableList.of( - new LootDefinition("generic") - .in(LootTables.ABANDONED_MINESHAFT) - .in(LootTables.BASTION_TREASURE) - .in(LootTables.BURIED_TREASURE) - .in(LootTables.DESERT_PYRAMID) - .in(LootTables.NETHER_BRIDGE) - .in(LootTables.RUINED_PORTAL) - .in(LootTables.SHIPWRECK_TREASURE) - .in(LootTables.STRONGHOLD_LIBRARY) - .in(LootTables.UNDERWATER_RUIN_BIG) - .in(LootTables.VILLAGE_TEMPLE) - .in(LootTables.WOODLAND_MANSION) - .add(1.0 / 40, 1, 1, key().toString()), - new LootDefinition("ancientcity") - .in(LootTables.ANCIENT_CITY) - .add(1.0 / 30, 1, 1, key().toString()) // duplicate for more consistent spawn - .add(1.0 / 30, 1, 1, key().toString()), - new LootDefinition("terralith_generic") - // terralith normal - .in(StorageUtil.namespaced_key("terralith", "ruin/glacial/main_cs")) - .in(StorageUtil.namespaced_key("terralith", "spire/treasure")) - .in(StorageUtil.namespaced_key("terralith", "underground/chest")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/archer")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/attic")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/butcher")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/cartographer")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/generic")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/library")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/mason")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/smith")) - .in(StorageUtil.namespaced_key("terralith", "village/desert/treasure")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/archer")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/attic")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/butcher")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/cartographer")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/fisherman")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/food")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/generic")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/library")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/mason")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/treasure")) - .in(StorageUtil.namespaced_key("terralith", "village/treasure/diamond")) - .in(StorageUtil.namespaced_key("terralith", "village/treasure/emerald")) - .in(StorageUtil.namespaced_key("terralith", "village/treasure/golem")) - .add(1.0 / 40, 1, 1, key().toString()), - new LootDefinition("terralith_rare") - // terralith rare - .in(StorageUtil.namespaced_key("terralith", "village/desert/smith/expert")) - .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith/expert")) - .in(StorageUtil.namespaced_key("terralith", "spire/rare")) - .add(1.0 / 30, 1, 1, key().toString()) // duplicate for more consistent spawn - .add(1.0 / 30, 1, 1, key().toString())); - } - } + public EnchantedAncientTome(Context context) { + super(context); + } + } - @VaneItem(name = "enchanted_ancient_tome_of_knowledge", base = Material.ENCHANTED_BOOK, model_data = 0x770003, version = 1) - public static class EnchantedAncientTomeOfKnowledge extends CustomItem { - public EnchantedAncientTomeOfKnowledge(Context context) { - super(context); - } - } + @VaneItem(name = "ancient_tome_of_knowledge", base = Material.BOOK, model_data = 0x770002, version = 1) + public static class AncientTomeOfKnowledge extends CustomItem { - @VaneItem(name = "ancient_tome_of_the_gods", base = Material.BOOK, model_data = 0x770004, version = 1) - public static class AncientTomeOfTheGods extends CustomItem { - public AncientTomeOfTheGods(Context context) { - super(context); - } + public AncientTomeOfKnowledge(Context context) { + super(context); + } - @Override - public RecipeList default_recipes() { - return RecipeList.of(new ShapedRecipeDefinition("generic") - .shape(" s ", "ebe", " n ") - .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") - .set_ingredient('e', Material.ENCHANTED_BOOK) - .set_ingredient('s', Material.NETHER_STAR) - .set_ingredient('n', Material.NAUTILUS_SHELL) - .result(key().toString())); - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapelessRecipeDefinition("generic") + .add_ingredient("vane_enchantments:ancient_tome") + .add_ingredient(Material.FEATHER) + .add_ingredient(Material.BLAZE_ROD) + .add_ingredient(Material.GHAST_TEAR) + .result(key().toString()) + ); + } - @Override - public LootTableList default_loot_tables() { - return LootTableList.of( - new LootDefinition("generic") - .in(LootTables.BASTION_TREASURE) - .in(LootTables.BURIED_TREASURE) - .in(LootTables.SHIPWRECK_TREASURE) - .in(LootTables.UNDERWATER_RUIN_BIG) - .add(1.0 / 200, 1, 1, key().toString()), - new LootDefinition("ancientcity") - .in(LootTables.ANCIENT_CITY) - .add(1.0 / 150, 1, 1, key().toString()), - new LootDefinition("terralith_generic") - // terralith normal - .in(StorageUtil.namespaced_key("terralith", "spire/treasure")) - .in(StorageUtil.namespaced_key("terralith", "underground/chest")) - .add(1.0 / 200, 1, 1, key().toString()), - new LootDefinition("terralith_rare") - // terralith rare - .in(StorageUtil.namespaced_key("terralith", "spire/rare")) - .add(1.0 / 150, 1, 1, key().toString())); - } - } + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.ABANDONED_MINESHAFT) + .in(LootTables.BASTION_TREASURE) + .in(LootTables.BURIED_TREASURE) + .in(LootTables.DESERT_PYRAMID) + .in(LootTables.NETHER_BRIDGE) + .in(LootTables.RUINED_PORTAL) + .in(LootTables.SHIPWRECK_TREASURE) + .in(LootTables.STRONGHOLD_LIBRARY) + .in(LootTables.UNDERWATER_RUIN_BIG) + .in(LootTables.VILLAGE_TEMPLE) + .in(LootTables.WOODLAND_MANSION) + .add(1.0 / 40, 1, 1, key().toString()), + new LootDefinition("ancientcity") + .in(LootTables.ANCIENT_CITY) + .add(1.0 / 30, 1, 1, key().toString()) // duplicate for more consistent spawn + .add(1.0 / 30, 1, 1, key().toString()), + new LootDefinition("terralith_generic") + // terralith normal + .in(StorageUtil.namespaced_key("terralith", "ruin/glacial/main_cs")) + .in(StorageUtil.namespaced_key("terralith", "spire/treasure")) + .in(StorageUtil.namespaced_key("terralith", "underground/chest")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/archer")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/attic")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/butcher")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/cartographer")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/generic")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/library")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/mason")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/smith")) + .in(StorageUtil.namespaced_key("terralith", "village/desert/treasure")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/archer")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/attic")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/butcher")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/cartographer")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/fisherman")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/food")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/generic")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/library")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/mason")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/treasure")) + .in(StorageUtil.namespaced_key("terralith", "village/treasure/diamond")) + .in(StorageUtil.namespaced_key("terralith", "village/treasure/emerald")) + .in(StorageUtil.namespaced_key("terralith", "village/treasure/golem")) + .add(1.0 / 40, 1, 1, key().toString()), + new LootDefinition("terralith_rare") + // terralith rare + .in(StorageUtil.namespaced_key("terralith", "village/desert/smith/expert")) + .in(StorageUtil.namespaced_key("terralith", "village/fortified/smith/expert")) + .in(StorageUtil.namespaced_key("terralith", "spire/rare")) + .add(1.0 / 30, 1, 1, key().toString()) // duplicate for more consistent spawn + .add(1.0 / 30, 1, 1, key().toString()) + ); + } + } - @VaneItem(name = "enchanted_ancient_tome_of_the_gods", base = Material.ENCHANTED_BOOK, model_data = 0x770005, version = 1) - public static class EnchantedAncientTomeOfTheGods extends CustomItem { - public EnchantedAncientTomeOfTheGods(Context context) { - super(context); - } - } + @VaneItem( + name = "enchanted_ancient_tome_of_knowledge", + base = Material.ENCHANTED_BOOK, + model_data = 0x770003, + version = 1 + ) + public static class EnchantedAncientTomeOfKnowledge extends CustomItem { - @VaneItem(name = "enchanted_ancient_tome_of_the_gods", base = Material.ENCHANTED_BOOK, model_data = 0x770005, version = 1) - public static class GrindstoneListener extends Listener { - public GrindstoneListener(Context context) { - super(context); - } + public EnchantedAncientTomeOfKnowledge(Context context) { + super(context); + } + } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_prepare_grindstone(PrepareGrindstoneEvent event) { - // Make sure to remove the enchanted variant when disenchanting a tome - var res = event.getResult(); - if (res == null) { - return; - } + @VaneItem(name = "ancient_tome_of_the_gods", base = Material.BOOK, model_data = 0x770004, version = 1) + public static class AncientTomeOfTheGods extends CustomItem { - // Only if there are no enchantments on an enchanted variant, we revert to - // non-enchanted variant - if (!res.getEnchantments().isEmpty()) { - return; - } + public AncientTomeOfTheGods(Context context) { + super(context); + } - final var custom_item = get_module().core.item_registry().get(res); - if (custom_item instanceof EnchantedAncientTome) { - event.setResult(CustomItemHelper.newStack("vane_enchantments:ancient_tome")); - } else if (custom_item instanceof EnchantedAncientTomeOfKnowledge) { - event.setResult(CustomItemHelper.newStack("vane_enchantments:ancient_tome_of_knowledge")); - } else if (custom_item instanceof EnchantedAncientTomeOfTheGods) { - event.setResult(CustomItemHelper.newStack("vane_enchantments:ancient_tome_of_the_gods")); - } - } - } + @Override + public RecipeList default_recipes() { + return RecipeList.of( + new ShapedRecipeDefinition("generic") + .shape(" s ", "ebe", " n ") + .set_ingredient('b', "vane_enchantments:ancient_tome_of_knowledge") + .set_ingredient('e', Material.ENCHANTED_BOOK) + .set_ingredient('s', Material.NETHER_STAR) + .set_ingredient('n', Material.NAUTILUS_SHELL) + .result(key().toString()) + ); + } + + @Override + public LootTableList default_loot_tables() { + return LootTableList.of( + new LootDefinition("generic") + .in(LootTables.BASTION_TREASURE) + .in(LootTables.BURIED_TREASURE) + .in(LootTables.SHIPWRECK_TREASURE) + .in(LootTables.UNDERWATER_RUIN_BIG) + .add(1.0 / 200, 1, 1, key().toString()), + new LootDefinition("ancientcity").in(LootTables.ANCIENT_CITY).add(1.0 / 150, 1, 1, key().toString()), + new LootDefinition("terralith_generic") + // terralith normal + .in(StorageUtil.namespaced_key("terralith", "spire/treasure")) + .in(StorageUtil.namespaced_key("terralith", "underground/chest")) + .add(1.0 / 200, 1, 1, key().toString()), + new LootDefinition("terralith_rare") + // terralith rare + .in(StorageUtil.namespaced_key("terralith", "spire/rare")) + .add(1.0 / 150, 1, 1, key().toString()) + ); + } + } + + @VaneItem( + name = "enchanted_ancient_tome_of_the_gods", + base = Material.ENCHANTED_BOOK, + model_data = 0x770005, + version = 1 + ) + public static class EnchantedAncientTomeOfTheGods extends CustomItem { + + public EnchantedAncientTomeOfTheGods(Context context) { + super(context); + } + } + + @VaneItem( + name = "enchanted_ancient_tome_of_the_gods", + base = Material.ENCHANTED_BOOK, + model_data = 0x770005, + version = 1 + ) + public static class GrindstoneListener extends Listener { + + public GrindstoneListener(Context context) { + super(context); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_prepare_grindstone(PrepareGrindstoneEvent event) { + // Make sure to remove the enchanted variant when disenchanting a tome + var res = event.getResult(); + if (res == null) { + return; + } + + // Only if there are no enchantments on an enchanted variant, we revert to + // non-enchanted variant + if (!res.getEnchantments().isEmpty()) { + return; + } + + final var custom_item = get_module().core.item_registry().get(res); + if (custom_item instanceof EnchantedAncientTome) { + event.setResult(CustomItemHelper.newStack("vane_enchantments:ancient_tome")); + } else if (custom_item instanceof EnchantedAncientTomeOfKnowledge) { + event.setResult(CustomItemHelper.newStack("vane_enchantments:ancient_tome_of_knowledge")); + } else if (custom_item instanceof EnchantedAncientTomeOfTheGods) { + event.setResult(CustomItemHelper.newStack("vane_enchantments:ancient_tome_of_the_gods")); + } + } + } } diff --git a/vane-permissions/src/main/java/org/oddlama/vane/permissions/Permissions.java b/vane-permissions/src/main/java/org/oddlama/vane/permissions/Permissions.java index f321c8c90..cab7894ca 100644 --- a/vane-permissions/src/main/java/org/oddlama/vane/permissions/Permissions.java +++ b/vane-permissions/src/main/java/org/oddlama/vane/permissions/Permissions.java @@ -9,7 +9,6 @@ import java.util.UUID; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -31,254 +30,252 @@ @VaneModule(name = "permissions", bstats = 8641, config_version = 1, lang_version = 1, storage_version = 1) public class Permissions extends Module { - // Configuration - @ConfigBoolean( - def = false, - desc = "Remove all default permissions from ANY SOURCE (including other plugins and minecraft permissions) to start with a clean preset. This will allow you to exactly set which player have which permissions instead of having to resort to volatile stateful changes like negative permissions. This will result in OPed players to lose access to commands, if not explicitly added back via permissions. The wildcard permissions can be viewed using `perms list permissions`. The wildcard permissions `minecraft` and `craftbukkit` may be especially useful." - ) - public boolean config_remove_defaults; - - @ConfigString( - def = "default", - desc = "The permission group that will be given to players that have no other permission group." - ) - public String config_default_group; - - @ConfigStringListMap( - def = { - @ConfigStringListMapEntry( - key = "default", - list = { "bukkit.command.help", "bukkit.broadcast", "bukkit.broadcast.user" } - ), - @ConfigStringListMapEntry( - key = "user", - list = { - "vane.permissions.groups.default", - "vane.admin.modify_world", - "vane.regions.commands.region", - "vane.trifles.commands.heads", - } - ), - @ConfigStringListMapEntry( - key = "verified", - list = { "vane.permissions.groups.user", "vane.permissions.commands.vouch" } - ), - @ConfigStringListMapEntry( - key = "admin", - list = { - "vane.permissions.groups.verified", - "vane.admin.bypass_spawn_protection", - "vane.portals.admin", - "vane.regions.admin", - "vane.*.commands.*", - } - ), - }, - desc = "The permission groups. A player can have multiple permission groups assigned. Permission groups can inherit other permission groups by specifying vane.permissions.groups. as a permission." - ) - public Map> config_groups; - - // Persistent storage - @Persistent - public Map> storage_player_groups = new HashMap<>(); - - // Variables - public final Map> permission_groups = new HashMap<>(); - private final Map player_attachments = new HashMap<>(); - - public Permissions() { - new org.oddlama.vane.permissions.commands.Permission(this); - new org.oddlama.vane.permissions.commands.Vouch(this); - } - - @Override - public void on_enable() { - schedule_next_tick(() -> { - if (config_remove_defaults) { - for (var perm : getServer().getPluginManager().getPermissions()) { - perm.setDefault(PermissionDefault.FALSE); - getServer().getPluginManager().recalculatePermissionDefaults(perm); - - // But still allow the console to execute commands - Permissions.this.add_console_permission(perm); - } - } - }); - } - - @Override - public void on_config_change() { - flatten_groups(); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_join(PlayerJoinEvent event) { - register_player(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_kick(PlayerKickEvent event) { - unregister_player(event.getPlayer()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_player_quit(PlayerQuitEvent event) { - unregister_player(event.getPlayer()); - } - - private final Map sender_attachments = new HashMap<>(); - - private void add_console_permissions(final CommandSender sender) { - // Register attachment for sender if not done already - if (!sender_attachments.containsKey(sender)) { - final var attachment = sender.addAttachment(this); - sender_attachments.put(sender, attachment); - - final var attached_perms = console_attachment.getPermissions(); - attached_perms.forEach((p, v) -> attachment.setPermission(p, v)); - } - } - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void on_server_command_event(ServerCommandEvent event) { - final var sender = event.getSender(); - if (sender instanceof Player && sender.isOp()) { - // Console command sender will always have the correct permission attachment - // Command block shall be ignored for now (causes lag, see #178) - add_console_permissions(sender); - } - } - - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void on_remote_server_command_event(RemoteServerCommandEvent event) { - final var sender = event.getSender(); - if (sender.isOp()) { - add_console_permissions(sender); - } - } - - /** Resolve references to other permission groups in the hierarchy. */ - private void flatten_groups() { - permission_groups.clear(); - config_groups.forEach((k, v) -> { - final var set = new HashSet(); - for (var perm : v) { - if (perm.startsWith("vane.permissions.groups.")) { - // Resolving will be delayed to second pass - } else { - set.add(perm); - } - } - permission_groups.put(k, set); - }); - - // Resolve group inheritance - var modified = new Object() { - boolean value = false; - }; - do { - modified.value = false; - config_groups.forEach((k, v) -> { - final var set = permission_groups.get(k); - for (var perm : v) { - if (perm.startsWith("vane.permissions.groups.")) { - final var group = perm.substring("vane.permissions.groups.".length()); - final var group_perms = permission_groups.get(group); - if (group_perms == null) { - log.severe( - "Nonexistent permission group '" + - group + - "' referenced by group '" + - k + - "'; Ignoring statement!" - ); - continue; - } - modified.value |= set.addAll(group_perms); - } - } - }); - } while (modified.value); - } - - private void register_player(final Player player) { - // Register PermissionAttachment - final var attachment = player.addAttachment(this); - player_attachments.put(player.getUniqueId(), attachment); - - // Attach permissions - recalculate_player_permissions(player); - } - - public void recalculate_player_permissions(final Player player) { - // Clear attachment - final var attachment = player_attachments.get(player.getUniqueId()); - final var attached_perms = attachment.getPermissions(); - attached_perms.forEach((p, v) -> attachment.unsetPermission(p)); - - // Add permissions again - var groups = storage_player_groups.get(player.getUniqueId()); - if (groups == null || groups.isEmpty()) { - // Assign player to a default permission group - groups = Set.of(config_default_group); - } - - for (var group : groups) { - for (var p : permission_groups.getOrDefault(group, Collections.emptySet())) { - final var perm = getServer().getPluginManager().getPermission(p); - if (perm == null) { - log.warning("Use of unregistered permission '" + p + "' might have unintended effects."); - } - attachment.setPermission(p, true); - } - } - - // Update list of commands for client side root tab completion - player.updateCommands(); - } - - private void unregister_player(final Player player) { - final var attachment = player_attachments.remove(player.getUniqueId()); - if (attachment != null) { - player.removeAttachment(attachment); - } - } - - public void save_and_recalculate(final OfflinePlayer player) { - mark_persistent_storage_dirty(); - - // Recalculate permissions if player is currently online - if (player.isOnline()) { - recalculate_player_permissions(player.getPlayer()); - } - } - - public boolean add_player_to_group(final OfflinePlayer player, final String group) { - var set = storage_player_groups.computeIfAbsent(player.getUniqueId(), k -> new HashSet()); - - final var added = set.add(group); - if (added) { - log.info("[audit] Group " + group + " assigned to " + player.getUniqueId() + " (" + player.getName() + ")"); - save_and_recalculate(player); - } - - return added; - } - - public boolean remove_player_from_group(final OfflinePlayer player, final String group) { - var set = storage_player_groups.get(player.getUniqueId()); - var removed = false; - if (set != null) { - removed = set.remove(group); - } - - if (removed) { - log.info( - "[audit] Group " + group + " removed from " + player.getUniqueId() + " (" + player.getName() + ")" - ); - save_and_recalculate(player); - } - - return removed; - } + // Configuration + @ConfigBoolean( + def = false, + desc = "Remove all default permissions from ANY SOURCE (including other plugins and minecraft permissions) to start with a clean preset. This will allow you to exactly set which player have which permissions instead of having to resort to volatile stateful changes like negative permissions. This will result in OPed players to lose access to commands, if not explicitly added back via permissions. The wildcard permissions can be viewed using `perms list permissions`. The wildcard permissions `minecraft` and `craftbukkit` may be especially useful." + ) + public boolean config_remove_defaults; + + @ConfigString( + def = "default", + desc = "The permission group that will be given to players that have no other permission group." + ) + public String config_default_group; + + @ConfigStringListMap( + def = { + @ConfigStringListMapEntry( + key = "default", + list = { "bukkit.command.help", "bukkit.broadcast", "bukkit.broadcast.user" } + ), + @ConfigStringListMapEntry( + key = "user", + list = { + "vane.permissions.groups.default", + "vane.admin.modify_world", + "vane.regions.commands.region", + "vane.trifles.commands.heads", + } + ), + @ConfigStringListMapEntry( + key = "verified", + list = { "vane.permissions.groups.user", "vane.permissions.commands.vouch" } + ), + @ConfigStringListMapEntry( + key = "admin", + list = { + "vane.permissions.groups.verified", + "vane.admin.bypass_spawn_protection", + "vane.portals.admin", + "vane.regions.admin", + "vane.*.commands.*", + } + ), + }, + desc = "The permission groups. A player can have multiple permission groups assigned. Permission groups can inherit other permission groups by specifying vane.permissions.groups. as a permission." + ) + public Map> config_groups; + + // Persistent storage + @Persistent + public Map> storage_player_groups = new HashMap<>(); + + // Variables + public final Map> permission_groups = new HashMap<>(); + private final Map player_attachments = new HashMap<>(); + + public Permissions() { + new org.oddlama.vane.permissions.commands.Permission(this); + new org.oddlama.vane.permissions.commands.Vouch(this); + } + + @Override + public void on_enable() { + schedule_next_tick(() -> { + if (config_remove_defaults) { + for (var perm : getServer().getPluginManager().getPermissions()) { + perm.setDefault(PermissionDefault.FALSE); + getServer().getPluginManager().recalculatePermissionDefaults(perm); + + // But still allow the console to execute commands + Permissions.this.add_console_permission(perm); + } + } + }); + } + + @Override + public void on_config_change() { + flatten_groups(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_join(PlayerJoinEvent event) { + register_player(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_kick(PlayerKickEvent event) { + unregister_player(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_player_quit(PlayerQuitEvent event) { + unregister_player(event.getPlayer()); + } + + private final Map sender_attachments = new HashMap<>(); + + private void add_console_permissions(final CommandSender sender) { + // Register attachment for sender if not done already + if (!sender_attachments.containsKey(sender)) { + final var attachment = sender.addAttachment(this); + sender_attachments.put(sender, attachment); + + final var attached_perms = console_attachment.getPermissions(); + attached_perms.forEach((p, v) -> attachment.setPermission(p, v)); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void on_server_command_event(ServerCommandEvent event) { + final var sender = event.getSender(); + if (sender instanceof Player && sender.isOp()) { + // Console command sender will always have the correct permission attachment + // Command block shall be ignored for now (causes lag, see #178) + add_console_permissions(sender); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void on_remote_server_command_event(RemoteServerCommandEvent event) { + final var sender = event.getSender(); + if (sender.isOp()) { + add_console_permissions(sender); + } + } + + /** Resolve references to other permission groups in the hierarchy. */ + private void flatten_groups() { + permission_groups.clear(); + config_groups.forEach((k, v) -> { + final var set = new HashSet(); + for (var perm : v) { + if (perm.startsWith("vane.permissions.groups.")) {} else { + set.add(perm); + } + } + permission_groups.put(k, set); + }); + + // Resolve group inheritance + var modified = new Object() { + boolean value = false; + }; + do { + modified.value = false; + config_groups.forEach((k, v) -> { + final var set = permission_groups.get(k); + for (var perm : v) { + if (perm.startsWith("vane.permissions.groups.")) { + final var group = perm.substring("vane.permissions.groups.".length()); + final var group_perms = permission_groups.get(group); + if (group_perms == null) { + log.severe( + "Nonexistent permission group '" + + group + + "' referenced by group '" + + k + + "'; Ignoring statement!" + ); + continue; + } + modified.value |= set.addAll(group_perms); + } + } + }); + } while (modified.value); + } + + private void register_player(final Player player) { + // Register PermissionAttachment + final var attachment = player.addAttachment(this); + player_attachments.put(player.getUniqueId(), attachment); + + // Attach permissions + recalculate_player_permissions(player); + } + + public void recalculate_player_permissions(final Player player) { + // Clear attachment + final var attachment = player_attachments.get(player.getUniqueId()); + final var attached_perms = attachment.getPermissions(); + attached_perms.forEach((p, v) -> attachment.unsetPermission(p)); + + // Add permissions again + var groups = storage_player_groups.get(player.getUniqueId()); + if (groups == null || groups.isEmpty()) { + // Assign player to a default permission group + groups = Set.of(config_default_group); + } + + for (var group : groups) { + for (var p : permission_groups.getOrDefault(group, Collections.emptySet())) { + final var perm = getServer().getPluginManager().getPermission(p); + if (perm == null) { + log.warning("Use of unregistered permission '" + p + "' might have unintended effects."); + } + attachment.setPermission(p, true); + } + } + + // Update list of commands for client side root tab completion + player.updateCommands(); + } + + private void unregister_player(final Player player) { + final var attachment = player_attachments.remove(player.getUniqueId()); + if (attachment != null) { + player.removeAttachment(attachment); + } + } + + public void save_and_recalculate(final OfflinePlayer player) { + mark_persistent_storage_dirty(); + + // Recalculate permissions if player is currently online + if (player.isOnline()) { + recalculate_player_permissions(player.getPlayer()); + } + } + + public boolean add_player_to_group(final OfflinePlayer player, final String group) { + var set = storage_player_groups.computeIfAbsent(player.getUniqueId(), k -> new HashSet()); + + final var added = set.add(group); + if (added) { + log.info("[audit] Group " + group + " assigned to " + player.getUniqueId() + " (" + player.getName() + ")"); + save_and_recalculate(player); + } + + return added; + } + + public boolean remove_player_from_group(final OfflinePlayer player, final String group) { + var set = storage_player_groups.get(player.getUniqueId()); + var removed = false; + if (set != null) { + removed = set.remove(group); + } + + if (removed) { + log.info( + "[audit] Group " + group + " removed from " + player.getUniqueId() + " (" + player.getName() + ")" + ); + save_and_recalculate(player); + } + + return removed; + } } diff --git a/vane-permissions/src/main/java/org/oddlama/vane/permissions/argumentTypes/PermissionGroupArgumentType.java b/vane-permissions/src/main/java/org/oddlama/vane/permissions/argumentTypes/PermissionGroupArgumentType.java index 7c7261714..ca934d36c 100644 --- a/vane-permissions/src/main/java/org/oddlama/vane/permissions/argumentTypes/PermissionGroupArgumentType.java +++ b/vane-permissions/src/main/java/org/oddlama/vane/permissions/argumentTypes/PermissionGroupArgumentType.java @@ -1,20 +1,17 @@ package org.oddlama.vane.permissions.argumentTypes; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -import org.jetbrains.annotations.NotNull; - import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; public class PermissionGroupArgumentType implements CustomArgumentType.Converted { @@ -39,15 +36,15 @@ private PermissionGroupArgumentType(Map> permission_groups) } @Override - public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, - @NotNull SuggestionsBuilder builder) { + public @NotNull CompletableFuture listSuggestions( + @NotNull CommandContext context, + @NotNull SuggestionsBuilder builder + ) { Stream stream = permission_groups.keySet().stream(); - if(!builder.getRemaining().isBlank()) { + if (!builder.getRemaining().isBlank()) { stream = stream.filter(group -> group.contains(builder.getRemaining())); } stream.forEach(builder::suggest); return builder.buildFuture(); } - - } diff --git a/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Permission.java b/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Permission.java index b0203a885..93306d1cf 100644 --- a/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Permission.java +++ b/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Permission.java @@ -4,6 +4,9 @@ import static io.papermc.paper.command.brigadier.Commands.argument; import static io.papermc.paper.command.brigadier.Commands.literal; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; import java.util.Collections; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; @@ -19,249 +22,280 @@ import org.oddlama.vane.permissions.Permissions; import org.oddlama.vane.permissions.argumentTypes.PermissionGroupArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; - -import io.papermc.paper.command.brigadier.CommandSourceStack; - @Name("permission") @Aliases({ "perm" }) public class Permission extends Command { - @LangMessage - private TranslatedMessage lang_list_empty; - - @LangMessage - private TranslatedMessage lang_list_header_groups; - - @LangMessage - private TranslatedMessage lang_list_header_permissions; - - @LangMessage - private TranslatedMessage lang_list_header_player_groups; - - @LangMessage - private TranslatedMessage lang_list_header_player_permissions; - - @LangMessage - private TranslatedMessage lang_list_header_group_permissions; - - @LangMessage - private TranslatedMessage lang_list_player_offline; - - @LangMessage - private TranslatedMessage lang_list_group; - - @LangMessage - private TranslatedMessage lang_list_permission; - - @LangMessage - private TranslatedMessage lang_group_assigned; - - @LangMessage - private TranslatedMessage lang_group_removed; - - @LangMessage - private TranslatedMessage lang_group_already_assigned; - - @LangMessage - private TranslatedMessage lang_group_not_assigned; - - public Permission(Context context) { - super(context); - } - - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .then(help()) - - .then(literal("list") - .then(literal("groups") - .executes(ctx -> {list_groups(ctx.getSource().getSender()); return SINGLE_SUCCESS;}) - .then(argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()) - .executes(ctx -> {list_groups_for_player(sender(ctx), offline_player(ctx)); return SINGLE_SUCCESS;}) - ) - ) - .then(literal("permissions") - // FIXME weirdly autocompletion works in the console but not in game ?? - .then(argument("permission_group", PermissionGroupArgumentType.permissionGroup(get_module().permission_groups)) - .executes(ctx -> {list_permissions_for_group(ctx.getSource().getSender(), permission_group(ctx)); return SINGLE_SUCCESS;}) - ) - .then(argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()) - .executes(ctx -> {list_permissions_for_player(ctx.getSource().getSender(), offline_player(ctx)); return SINGLE_SUCCESS;}) - ) - .executes(ctx -> {list_permissions(ctx.getSource().getSender()); return SINGLE_SUCCESS;}) - ) - ) - - .then(literal("add") - .then(argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()) - .then(argument("permission_group", PermissionGroupArgumentType.permissionGroup(get_module().permission_groups)) - .executes(ctx -> {add_player_to_group(ctx.getSource().getSender(), offline_player(ctx), permission_group(ctx)); return SINGLE_SUCCESS;}) - ) - ) - ) - .then(literal("remove") - .then(argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()) - .then(argument("permission_group", PermissionGroupArgumentType.permissionGroup(get_module().permission_groups)) - .executes(ctx -> {remove_player_from_group(ctx.getSource().getSender(), offline_player(ctx), permission_group(ctx)); return SINGLE_SUCCESS;}) - ) - ) - ) - ; - } - - private String permission_group(CommandContext ctx){ - return ctx.getArgument("permission_group", String.class); - } - - private Player sender(CommandContext ctx){ - return (Player) ctx.getSource().getSender(); - } - - private OfflinePlayer offline_player(CommandContext ctx){ - return ctx.getArgument("offline_player", OfflinePlayer.class); - } - - private String permission_default_value_color_code(PermissionDefault def) { - switch (def) { - default: - return "§6"; - case FALSE: - return "§c"; - case NOT_OP: - return "§5"; - case OP: - return "§b"; - case TRUE: - return "§a"; - } - } - - private String permission_value_color_code(boolean value) { - return permission_default_value_color_code(value ? PermissionDefault.TRUE : PermissionDefault.FALSE); - } - - private void list_groups(CommandSender sender) { - lang_list_header_groups.send(sender); - get_module() - .permission_groups.keySet() - .stream() - .sorted((a, b) -> a.compareTo(b)) - .forEach(group -> lang_list_group.send(sender, "§b" + group)); - } - - private void list_permissions(CommandSender sender) { - lang_list_header_permissions.send(sender); - get_module() - .getServer() - .getPluginManager() - .getPermissions() - .stream() - .sorted((a, b) -> a.getName().compareTo(b.getName())) - .forEach(perm -> lang_list_permission.send( - sender, - "§d" + perm.getName(), - permission_default_value_color_code(perm.getDefault()) + perm.getDefault().toString().toLowerCase(), - perm.getDescription() - )); - } - - private void list_permissions_for_player(CommandSender sender, OfflinePlayer offline_player) { - lang_list_header_player_permissions.send(sender, "§b" + offline_player.getName()); - var player = offline_player.getPlayer(); - if (player == null) { - // Player is offline, show configured permissions only. - // Information from other plugins might be missing. - lang_list_player_offline.send(sender); - final var groups = get_module().storage_player_groups.get(offline_player.getUniqueId()); - if (groups == null) { - lang_list_empty.send(sender); - } else { - for (var group : groups) { - list_permissions_for_group_no_header(sender, group); - } - } - } else { - var effective_permissions = player.getEffectivePermissions(); - if (effective_permissions.isEmpty()) { - lang_list_empty.send(sender); - } else { - player - .getEffectivePermissions() - .stream() - .sorted((a, b) -> a.getPermission().compareTo(b.getPermission())) - .forEach(att -> { - var perm = get_module().getServer().getPluginManager().getPermission(att.getPermission()); - if (perm == null) { - get_module() - .log.warning("Encountered unregistered permission '" + att.getPermission() + "'"); - return; - } - lang_list_permission.send( - sender, - "§d" + perm.getName(), - permission_value_color_code(att.getValue()) + att.getValue(), - perm.getDescription() - ); - }); - } - } - } - - private void list_permissions_for_group_no_header(CommandSender sender, String group) { - for (var p : get_module().permission_groups.getOrDefault(group, Collections.emptySet())) { - var perm = get_module().getServer().getPluginManager().getPermission(p); - if (perm == null) { - get_module().log.warning("Use of unregistered permission '" + p + "' might have unintended effects."); - lang_list_permission.send( - sender, - "§d" + p, - permission_value_color_code(true) + true, - "" - ); - } else { - lang_list_permission.send( - sender, - "§d" + perm.getName(), - permission_value_color_code(true) + true, - perm.getDescription() - ); - } - } - } - - private void list_permissions_for_group(CommandSender sender, String group) { - lang_list_header_group_permissions.send(sender, "§b" + group); - list_permissions_for_group_no_header(sender, group); - } - - private void list_groups_for_player(CommandSender sender, OfflinePlayer offline_player) { - var set = get_module().storage_player_groups.get(offline_player.getUniqueId()); - if (set == null) { - lang_list_empty.send(sender); - } else { - lang_list_header_player_groups.send(sender, "§b" + offline_player.getName()); - for (var group : set) { - lang_list_group.send(sender, group); - } - } - } - - private void add_player_to_group(final CommandSender sender, final OfflinePlayer player, final String group) { - if (get_module().add_player_to_group(player, group)) { - lang_group_assigned.send(sender, "§b" + player.getName(), "§a" + group); - } else { - lang_group_already_assigned.send(sender, "§b" + player.getName(), "§a" + group); - } - } - - private void remove_player_from_group(final CommandSender sender, final OfflinePlayer player, final String group) { - if (get_module().remove_player_from_group(player, group)) { - lang_group_removed.send(sender, "§b" + player.getName(), "§a" + group); - } else { - lang_group_not_assigned.send(sender, "§b" + player.getName(), "§a" + group); - } - } + @LangMessage + private TranslatedMessage lang_list_empty; + + @LangMessage + private TranslatedMessage lang_list_header_groups; + + @LangMessage + private TranslatedMessage lang_list_header_permissions; + + @LangMessage + private TranslatedMessage lang_list_header_player_groups; + + @LangMessage + private TranslatedMessage lang_list_header_player_permissions; + + @LangMessage + private TranslatedMessage lang_list_header_group_permissions; + + @LangMessage + private TranslatedMessage lang_list_player_offline; + + @LangMessage + private TranslatedMessage lang_list_group; + + @LangMessage + private TranslatedMessage lang_list_permission; + + @LangMessage + private TranslatedMessage lang_group_assigned; + + @LangMessage + private TranslatedMessage lang_group_removed; + + @LangMessage + private TranslatedMessage lang_group_already_assigned; + + @LangMessage + private TranslatedMessage lang_group_not_assigned; + + public Permission(Context context) { + super(context); + } + + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .then(help()) + .then( + literal("list") + .then( + literal("groups") + .executes(ctx -> { + list_groups(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + .then( + argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()).executes(ctx -> { + list_groups_for_player(sender(ctx), offline_player(ctx)); + return SINGLE_SUCCESS; + }) + ) + ) + .then( + literal("permissions") + // FIXME weirdly autocompletion works in the console + // but not in game ?? + .then( + argument( + "permission_group", + PermissionGroupArgumentType.permissionGroup(get_module().permission_groups) + ).executes(ctx -> { + list_permissions_for_group(ctx.getSource().getSender(), permission_group(ctx)); + return SINGLE_SUCCESS; + }) + ) + .then( + argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()).executes(ctx -> { + list_permissions_for_player(ctx.getSource().getSender(), offline_player(ctx)); + return SINGLE_SUCCESS; + }) + ) + .executes(ctx -> { + list_permissions(ctx.getSource().getSender()); + return SINGLE_SUCCESS; + }) + ) + ) + .then( + literal("add").then( + argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()).then( + argument( + "permission_group", + PermissionGroupArgumentType.permissionGroup(get_module().permission_groups) + ).executes(ctx -> { + add_player_to_group( + ctx.getSource().getSender(), + offline_player(ctx), + permission_group(ctx) + ); + return SINGLE_SUCCESS; + }) + ) + ) + ) + .then( + literal("remove").then( + argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()).then( + argument( + "permission_group", + PermissionGroupArgumentType.permissionGroup(get_module().permission_groups) + ).executes(ctx -> { + remove_player_from_group( + ctx.getSource().getSender(), + offline_player(ctx), + permission_group(ctx) + ); + return SINGLE_SUCCESS; + }) + ) + ) + ); + } + + private String permission_group(CommandContext ctx) { + return ctx.getArgument("permission_group", String.class); + } + + private Player sender(CommandContext ctx) { + return (Player) ctx.getSource().getSender(); + } + + private OfflinePlayer offline_player(CommandContext ctx) { + return ctx.getArgument("offline_player", OfflinePlayer.class); + } + + private String permission_default_value_color_code(PermissionDefault def) { + switch (def) { + default: + return "§6"; + case FALSE: + return "§c"; + case NOT_OP: + return "§5"; + case OP: + return "§b"; + case TRUE: + return "§a"; + } + } + + private String permission_value_color_code(boolean value) { + return permission_default_value_color_code(value ? PermissionDefault.TRUE : PermissionDefault.FALSE); + } + + private void list_groups(CommandSender sender) { + lang_list_header_groups.send(sender); + get_module() + .permission_groups.keySet() + .stream() + .sorted((a, b) -> a.compareTo(b)) + .forEach(group -> lang_list_group.send(sender, "§b" + group)); + } + + private void list_permissions(CommandSender sender) { + lang_list_header_permissions.send(sender); + get_module() + .getServer() + .getPluginManager() + .getPermissions() + .stream() + .sorted((a, b) -> a.getName().compareTo(b.getName())) + .forEach(perm -> + lang_list_permission.send( + sender, + "§d" + perm.getName(), + permission_default_value_color_code(perm.getDefault()) + perm.getDefault().toString().toLowerCase(), + perm.getDescription() + ) + ); + } + + private void list_permissions_for_player(CommandSender sender, OfflinePlayer offline_player) { + lang_list_header_player_permissions.send(sender, "§b" + offline_player.getName()); + var player = offline_player.getPlayer(); + if (player == null) { + // Player is offline, show configured permissions only. + // Information from other plugins might be missing. + lang_list_player_offline.send(sender); + final var groups = get_module().storage_player_groups.get(offline_player.getUniqueId()); + if (groups == null) { + lang_list_empty.send(sender); + } else { + for (var group : groups) { + list_permissions_for_group_no_header(sender, group); + } + } + } else { + var effective_permissions = player.getEffectivePermissions(); + if (effective_permissions.isEmpty()) { + lang_list_empty.send(sender); + } else { + player + .getEffectivePermissions() + .stream() + .sorted((a, b) -> a.getPermission().compareTo(b.getPermission())) + .forEach(att -> { + var perm = get_module().getServer().getPluginManager().getPermission(att.getPermission()); + if (perm == null) { + get_module() + .log.warning("Encountered unregistered permission '" + att.getPermission() + "'"); + return; + } + lang_list_permission.send( + sender, + "§d" + perm.getName(), + permission_value_color_code(att.getValue()) + att.getValue(), + perm.getDescription() + ); + }); + } + } + } + + private void list_permissions_for_group_no_header(CommandSender sender, String group) { + for (var p : get_module().permission_groups.getOrDefault(group, Collections.emptySet())) { + var perm = get_module().getServer().getPluginManager().getPermission(p); + if (perm == null) { + get_module().log.warning("Use of unregistered permission '" + p + "' might have unintended effects."); + lang_list_permission.send(sender, "§d" + p, permission_value_color_code(true) + true, ""); + } else { + lang_list_permission.send( + sender, + "§d" + perm.getName(), + permission_value_color_code(true) + true, + perm.getDescription() + ); + } + } + } + + private void list_permissions_for_group(CommandSender sender, String group) { + lang_list_header_group_permissions.send(sender, "§b" + group); + list_permissions_for_group_no_header(sender, group); + } + + private void list_groups_for_player(CommandSender sender, OfflinePlayer offline_player) { + var set = get_module().storage_player_groups.get(offline_player.getUniqueId()); + if (set == null) { + lang_list_empty.send(sender); + } else { + lang_list_header_player_groups.send(sender, "§b" + offline_player.getName()); + for (var group : set) { + lang_list_group.send(sender, group); + } + } + } + + private void add_player_to_group(final CommandSender sender, final OfflinePlayer player, final String group) { + if (get_module().add_player_to_group(player, group)) { + lang_group_assigned.send(sender, "§b" + player.getName(), "§a" + group); + } else { + lang_group_already_assigned.send(sender, "§b" + player.getName(), "§a" + group); + } + } + + private void remove_player_from_group(final CommandSender sender, final OfflinePlayer player, final String group) { + if (get_module().remove_player_from_group(player, group)) { + lang_group_removed.send(sender, "§b" + player.getName(), "§a" + group); + } else { + lang_group_not_assigned.send(sender, "§b" + player.getName(), "§a" + group); + } + } } diff --git a/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Vouch.java b/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Vouch.java index 8abf6e9b9..8a26e73d7 100644 --- a/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Vouch.java +++ b/vane-permissions/src/main/java/org/oddlama/vane/permissions/commands/Vouch.java @@ -3,6 +3,8 @@ import static com.mojang.brigadier.Command.SINGLE_SUCCESS; import static io.papermc.paper.command.brigadier.Commands.argument; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -20,54 +22,56 @@ import org.oddlama.vane.core.module.Context; import org.oddlama.vane.permissions.Permissions; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; - -import io.papermc.paper.command.brigadier.CommandSourceStack; - @Name("vouch") public class Vouch extends Command { - @LangMessage - private TranslatedMessage lang_vouched; + @LangMessage + private TranslatedMessage lang_vouched; - @LangMessage - private TranslatedMessage lang_already_vouched; + @LangMessage + private TranslatedMessage lang_already_vouched; - @ConfigString(def = "user", desc = "The group to assign to players when someone vouches for them.", metrics = true) - private String config_vouch_group; + @ConfigString(def = "user", desc = "The group to assign to players when someone vouches for them.", metrics = true) + private String config_vouch_group; - // Persistent storage - @Persistent - public Map> storage_vouched_by = new HashMap<>(); + // Persistent storage + @Persistent + public Map> storage_vouched_by = new HashMap<>(); - public Vouch(Context context) { - super(context); - } + public Vouch(Context context) { + super(context); + } - @Override - public LiteralArgumentBuilder get_command_base() { - return super.get_command_base() - .then(help()) - .then(argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()) - .executes(ctx -> {vouch_for_player((Player) ctx.getSource().getSender(), ctx.getArgument("offline_player", OfflinePlayer.class)); return SINGLE_SUCCESS;}) - ); - } + @Override + public LiteralArgumentBuilder get_command_base() { + return super.get_command_base() + .then(help()) + .then( + argument("offline_player", OfflinePlayerArgumentType.offlinePlayer()).executes(ctx -> { + vouch_for_player( + (Player) ctx.getSource().getSender(), + ctx.getArgument("offline_player", OfflinePlayer.class) + ); + return SINGLE_SUCCESS; + }) + ); + } - private void vouch_for_player(final Player sender, final OfflinePlayer vouched_player) { - var vouched_by_set = storage_vouched_by.computeIfAbsent(vouched_player.getUniqueId(), k -> new HashSet<>()); + private void vouch_for_player(final Player sender, final OfflinePlayer vouched_player) { + var vouched_by_set = storage_vouched_by.computeIfAbsent(vouched_player.getUniqueId(), k -> new HashSet<>()); - if (vouched_by_set.add(sender.getUniqueId())) { - // If it was the first one, we assign the group, - // otherwise we just record that the player also vouched. - if (vouched_by_set.size() == 1) { - get_module().add_player_to_group(vouched_player, config_vouch_group); - } + if (vouched_by_set.add(sender.getUniqueId())) { + // If it was the first one, we assign the group, + // otherwise we just record that the player also vouched. + if (vouched_by_set.size() == 1) { + get_module().add_player_to_group(vouched_player, config_vouch_group); + } - lang_vouched.send(sender, "§b" + vouched_player.getName()); - } else { - lang_already_vouched.send(sender, "§b" + vouched_player.getName()); - } + lang_vouched.send(sender, "§b" + vouched_player.getName()); + } else { + lang_already_vouched.send(sender, "§b" + vouched_player.getName()); + } - mark_persistent_storage_dirty(); - } + mark_persistent_storage_dirty(); + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/EntityMoveProcessor.java b/vane-portals/src/main/java/org/oddlama/vane/portals/EntityMoveProcessor.java index 775bb623e..47dfb7285 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/EntityMoveProcessor.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/EntityMoveProcessor.java @@ -1,150 +1,158 @@ package org.oddlama.vane.portals; +import com.google.common.collect.Sets; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.UUID; - -import com.google.common.collect.Sets; - import org.apache.commons.lang3.tuple.Pair; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.scheduler.BukkitTask; -import org.oddlama.vane.portals.event.EntityMoveEvent; import org.oddlama.vane.core.module.Context; import org.oddlama.vane.core.module.ModuleComponent; +import org.oddlama.vane.portals.event.EntityMoveEvent; public class EntityMoveProcessor extends ModuleComponent { - // This is the queue of entity move events that need processing. - // It is a linked hash map, so we can update moved entity positions - // without changing iteration order. Processed entries will be removed from - // the front, and new entities are added to the back. If an entity moves twice - // but wasn't processed, we don't need to update it. This ensures that no entities - // will be accidentally skipped when we are struggling to keep up. - // This stores entity_id -> (entity, old location). - private LinkedHashMap> move_event_processing_queue = new LinkedHashMap<>(); - - // Two hash maps to store old and current positions for each entity. - private HashMap> move_event_current_positions = new HashMap<>(); - private HashMap> move_event_old_positions = new HashMap<>(); - - private BukkitTask task; - - // Never process entity-move events for more than ~30% of a tick. - // We use 15ms threshold time, and 50ms would be 1 tick. - private static final long move_event_max_nanoseconds_per_tick = 15000000l; - - public EntityMoveProcessor(Context context) { - super(context); - } - - private static boolean is_movement(final Location l1, final Location l2) { - // Different worlds = not a movement event. - return l1.getWorld() == l2.getWorld() && ( - l1.getX() != l2.getX() - || l1.getY() != l2.getY() - || l1.getZ() != l2.getZ() - || l1.getPitch() != l2.getPitch() - || l1.getYaw() != l2.getYaw()); - } - - private void process_entity_movements() { - // This custom event detector is necessary as PaperMC's entity move events trigger for LivingEntities, - // but we need move events for all entities. Wanna throw that potion through the portal? - // Yes. Shoot players through a portal? Ohh, definitely. Throw junk right into their bases? Abso-fucking-lutely. - - // This implementation uses a priority queue and a small - // scheduling algorithm to prevent this function from ever causing lags. - // Lags caused by other plugins or external means will inherently cause - // the entity movement event tickrate to be slowed down. - // - // This function is called every tick and has two main phases. - // - // 1. Detect entity movement and queue entities for processing. - // 2. Iterate through entities that moved in FIFO order - // and call event handlers, but make sure to immediately abort - // processing after exceeding a threshold time. This ensures - // that it will always at least process one entity, but never - // hog any performance from other tasks. - - // Phase 1 - Movement detection - // -------------------------------------------- - - final var active_portal_worlds = new HashSet(); - for (final var portal : get_module().all_available_portals()) { - if (get_module().is_activated(portal)) { - active_portal_worlds.add(portal.spawn_world()); - } - } - - // Store current positions for each entity - for (final var world_id : active_portal_worlds) { - final var world = get_module().getServer().getWorld(world_id); - if (world != null) { - for (final var entity : world.getEntities()) { - move_event_current_positions.put(entity.getUniqueId(), Pair.of(entity, entity.getLocation())); - } - } - } - - // For each entity that has an old position (computed efficiently via Sets.intersection), - // but isn't yet contained in the entities to process, we check whether the position - // has changed. If so, we add the entity to the processing queue. - // If the processing queue already contained the entity, we remove it before iterating - // as there is nothing to do - we simply lose information about the intermediate position. - for (final var eid : Sets.difference( - Sets.intersection(move_event_old_positions.keySet(), move_event_current_positions.keySet()), - move_event_processing_queue.keySet())) { - final var old_entity_and_loc = move_event_old_positions.get(eid); - final var new_entity_and_loc = move_event_current_positions.get(eid); - if (old_entity_and_loc == null || new_entity_and_loc == null || !is_movement(old_entity_and_loc.getRight(), new_entity_and_loc.getRight())) { - continue; - } - - move_event_processing_queue.put(eid, Pair.of(old_entity_and_loc)); - } - - // Swap old and current position hash maps, and only retain the now-old positions. - // This avoids unnecessary allocations. - final var tmp = move_event_current_positions; - move_event_current_positions = move_event_old_positions; - move_event_old_positions = tmp; - move_event_current_positions.clear(); - - // Phase 2 - Event dispatching - // -------------------------------------------- - - final var time_begin = System.nanoTime(); - final var pm = get_module().getServer().getPluginManager(); - final var iter = move_event_processing_queue.entrySet().iterator(); - while (iter.hasNext()) { - final var e_and_old_loc = iter.next().getValue(); - iter.remove(); - - // Dispatch event. - final var entity = e_and_old_loc.getLeft(); - final var event = new EntityMoveEvent(entity, e_and_old_loc.getRight(), entity.getLocation()); - pm.callEvent(event); - - // Abort if we exceed the threshold time - final var time_now = System.nanoTime(); - if (time_now - time_begin > move_event_max_nanoseconds_per_tick) { - break; - } - } - } - - @Override - protected void on_enable() { - // Each tick we need to recalculate whether entities moved. - // This is using a scheduling algorithm (see function implementation) to - // keep it lightweight and to prevent lags. - task = schedule_task_timer(this::process_entity_movements, 1l, 1l); - } - - @Override - protected void on_disable() { - task.cancel(); - } + + // This is the queue of entity move events that need processing. + // It is a linked hash map, so we can update moved entity positions + // without changing iteration order. Processed entries will be removed from + // the front, and new entities are added to the back. If an entity moves twice + // but wasn't processed, we don't need to update it. This ensures that no entities + // will be accidentally skipped when we are struggling to keep up. + // This stores entity_id -> (entity, old location). + private LinkedHashMap> move_event_processing_queue = new LinkedHashMap<>(); + + // Two hash maps to store old and current positions for each entity. + private HashMap> move_event_current_positions = new HashMap<>(); + private HashMap> move_event_old_positions = new HashMap<>(); + + private BukkitTask task; + + // Never process entity-move events for more than ~30% of a tick. + // We use 15ms threshold time, and 50ms would be 1 tick. + private static final long move_event_max_nanoseconds_per_tick = 15000000l; + + public EntityMoveProcessor(Context context) { + super(context); + } + + private static boolean is_movement(final Location l1, final Location l2) { + // Different worlds = not a movement event. + return ( + l1.getWorld() == l2.getWorld() && + (l1.getX() != l2.getX() || + l1.getY() != l2.getY() || + l1.getZ() != l2.getZ() || + l1.getPitch() != l2.getPitch() || + l1.getYaw() != l2.getYaw()) + ); + } + + private void process_entity_movements() { + // This custom event detector is necessary as PaperMC's entity move events trigger for + // LivingEntities, + // but we need move events for all entities. Wanna throw that potion through the portal? + // Yes. Shoot players through a portal? Ohh, definitely. Throw junk right into their bases? + // Abso-fucking-lutely. + + // This implementation uses a priority queue and a small + // scheduling algorithm to prevent this function from ever causing lags. + // Lags caused by other plugins or external means will inherently cause + // the entity movement event tickrate to be slowed down. + // + // This function is called every tick and has two main phases. + // + // 1. Detect entity movement and queue entities for processing. + // 2. Iterate through entities that moved in FIFO order + // and call event handlers, but make sure to immediately abort + // processing after exceeding a threshold time. This ensures + // that it will always at least process one entity, but never + // hog any performance from other tasks. + + // Phase 1 - Movement detection + // -------------------------------------------- + + final var active_portal_worlds = new HashSet(); + for (final var portal : get_module().all_available_portals()) { + if (get_module().is_activated(portal)) { + active_portal_worlds.add(portal.spawn_world()); + } + } + + // Store current positions for each entity + for (final var world_id : active_portal_worlds) { + final var world = get_module().getServer().getWorld(world_id); + if (world != null) { + for (final var entity : world.getEntities()) { + move_event_current_positions.put(entity.getUniqueId(), Pair.of(entity, entity.getLocation())); + } + } + } + + // For each entity that has an old position (computed efficiently via Sets.intersection), + // but isn't yet contained in the entities to process, we check whether the position + // has changed. If so, we add the entity to the processing queue. + // If the processing queue already contained the entity, we remove it before iterating + // as there is nothing to do - we simply lose information about the intermediate position. + for (final var eid : Sets.difference( + Sets.intersection(move_event_old_positions.keySet(), move_event_current_positions.keySet()), + move_event_processing_queue.keySet() + )) { + final var old_entity_and_loc = move_event_old_positions.get(eid); + final var new_entity_and_loc = move_event_current_positions.get(eid); + if ( + old_entity_and_loc == null || + new_entity_and_loc == null || + !is_movement(old_entity_and_loc.getRight(), new_entity_and_loc.getRight()) + ) { + continue; + } + + move_event_processing_queue.put(eid, Pair.of(old_entity_and_loc)); + } + + // Swap old and current position hash maps, and only retain the now-old positions. + // This avoids unnecessary allocations. + final var tmp = move_event_current_positions; + move_event_current_positions = move_event_old_positions; + move_event_old_positions = tmp; + move_event_current_positions.clear(); + + // Phase 2 - Event dispatching + // -------------------------------------------- + + final var time_begin = System.nanoTime(); + final var pm = get_module().getServer().getPluginManager(); + final var iter = move_event_processing_queue.entrySet().iterator(); + while (iter.hasNext()) { + final var e_and_old_loc = iter.next().getValue(); + iter.remove(); + + // Dispatch event. + final var entity = e_and_old_loc.getLeft(); + final var event = new EntityMoveEvent(entity, e_and_old_loc.getRight(), entity.getLocation()); + pm.callEvent(event); + + // Abort if we exceed the threshold time + final var time_now = System.nanoTime(); + if (time_now - time_begin > move_event_max_nanoseconds_per_tick) { + break; + } + } + } + + @Override + protected void on_enable() { + // Each tick we need to recalculate whether entities moved. + // This is using a scheduling algorithm (see function implementation) to + // keep it lightweight and to prevent lags. + task = schedule_task_timer(this::process_entity_movements, 1l, 1l); + } + + @Override + protected void on_disable() { + task.cancel(); + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalActivator.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalActivator.java index d4c1d4bc9..61d47beba 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalActivator.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalActivator.java @@ -19,128 +19,128 @@ public class PortalActivator extends Listener { - public PortalActivator(Context context) { - super(context); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = false) - public void on_player_interact_console(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - if (event.useInteractedBlock() == Event.Result.DENY) { - return; - } - - // Abort if the table is not a console - final var block = event.getClickedBlock(); - final var portal_block = get_module().portal_block_for(block); - if (portal_block == null || portal_block.type() != PortalBlock.Type.CONSOLE) { - return; - } - - event.setUseInteractedBlock(Event.Result.DENY); - event.setUseItemInHand(Event.Result.DENY); - - final var player = event.getPlayer(); - final var portal = get_module().portal_for(portal_block); - if (portal.open_console(get_module(), player, block)) { - swing_arm(player, event.getHand()); - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_interact_switch(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - if (event.useInteractedBlock() == Event.Result.DENY) { - return; - } - - final var block = event.getClickedBlock(); - final boolean allow_disable; - if (block.getType() == Material.LEVER) { - allow_disable = true; - } else if (Tag.BUTTONS.isTagged(block.getType())) { - allow_disable = false; - } else { - return; - } - - // Get base block the switch is attached to - final var bswitch = (Switch) block.getBlockData(); - final BlockFace attached_face; - switch (bswitch.getAttachedFace()) { - default: - case WALL: - attached_face = bswitch.getFacing().getOppositeFace(); - break; - case CEILING: - attached_face = BlockFace.UP; - break; - case FLOOR: - attached_face = BlockFace.DOWN; - break; - } - - // Find controlled portal - final var base = block.getRelative(attached_face); - final var portal = get_module().controlled_portal(base); - if (portal == null) { - return; - } - - final var player = event.getPlayer(); - final var active = get_module().is_activated(portal); - if (bswitch.isPowered() && allow_disable) { - if (!active) { - return; - } - - // Switch is being switched off → deactivate - if (!portal.deactivate(get_module(), player)) { - event.setUseInteractedBlock(Event.Result.DENY); - event.setUseItemInHand(Event.Result.DENY); - } - } else { - if (active) { - return; - } - - // Switch is being switched on → activate - if (!portal.activate(get_module(), player)) { - event.setUseInteractedBlock(Event.Result.DENY); - event.setUseItemInHand(Event.Result.DENY); - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_block_redstone(final BlockRedstoneEvent event) { - // Only on rising edge. - if (event.getOldCurrent() != 0 || event.getNewCurrent() == 0) { - return; - } - - // Only repeaters - final var block = event.getBlock(); - if (block.getType() != Material.REPEATER) { - return; - } - - // Get the block it's pointing towards. (Opposite of block's facing for repeaters) - final var repeater = (Repeater) block.getBlockData(); - final var into_block = block.getRelative(repeater.getFacing().getOppositeFace()); - - // Find controlled portal - final var portal = get_module().portal_for(into_block); - if (portal == null) { - return; - } - - portal.activate(get_module(), null); - } + public PortalActivator(Context context) { + super(context); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = false) + public void on_player_interact_console(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + if (event.useInteractedBlock() == Event.Result.DENY) { + return; + } + + // Abort if the table is not a console + final var block = event.getClickedBlock(); + final var portal_block = get_module().portal_block_for(block); + if (portal_block == null || portal_block.type() != PortalBlock.Type.CONSOLE) { + return; + } + + event.setUseInteractedBlock(Event.Result.DENY); + event.setUseItemInHand(Event.Result.DENY); + + final var player = event.getPlayer(); + final var portal = get_module().portal_for(portal_block); + if (portal.open_console(get_module(), player, block)) { + swing_arm(player, event.getHand()); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_interact_switch(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + if (event.useInteractedBlock() == Event.Result.DENY) { + return; + } + + final var block = event.getClickedBlock(); + final boolean allow_disable; + if (block.getType() == Material.LEVER) { + allow_disable = true; + } else if (Tag.BUTTONS.isTagged(block.getType())) { + allow_disable = false; + } else { + return; + } + + // Get base block the switch is attached to + final var bswitch = (Switch) block.getBlockData(); + final BlockFace attached_face; + switch (bswitch.getAttachedFace()) { + default: + case WALL: + attached_face = bswitch.getFacing().getOppositeFace(); + break; + case CEILING: + attached_face = BlockFace.UP; + break; + case FLOOR: + attached_face = BlockFace.DOWN; + break; + } + + // Find controlled portal + final var base = block.getRelative(attached_face); + final var portal = get_module().controlled_portal(base); + if (portal == null) { + return; + } + + final var player = event.getPlayer(); + final var active = get_module().is_activated(portal); + if (bswitch.isPowered() && allow_disable) { + if (!active) { + return; + } + + // Switch is being switched off → deactivate + if (!portal.deactivate(get_module(), player)) { + event.setUseInteractedBlock(Event.Result.DENY); + event.setUseItemInHand(Event.Result.DENY); + } + } else { + if (active) { + return; + } + + // Switch is being switched on → activate + if (!portal.activate(get_module(), player)) { + event.setUseInteractedBlock(Event.Result.DENY); + event.setUseItemInHand(Event.Result.DENY); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_block_redstone(final BlockRedstoneEvent event) { + // Only on rising edge. + if (event.getOldCurrent() != 0 || event.getNewCurrent() == 0) { + return; + } + + // Only repeaters + final var block = event.getBlock(); + if (block.getType() != Material.REPEATER) { + return; + } + + // Get the block it's pointing towards. (Opposite of block's facing for repeaters) + final var repeater = (Repeater) block.getBlockData(); + final var into_block = block.getRelative(repeater.getFacing().getOppositeFace()); + + // Find controlled portal + final var portal = get_module().portal_for(into_block); + if (portal == null) { + return; + } + + portal.activate(get_module(), null); + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlockProtector.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlockProtector.java index c2a161ca7..892526fa1 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlockProtector.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlockProtector.java @@ -13,59 +13,59 @@ public class PortalBlockProtector extends Listener { - public PortalBlockProtector(Context context) { - super(context); - } + public PortalBlockProtector(Context context) { + super(context); + } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_block_break(final BlockBreakEvent event) { - // Prevent breaking of portal blocks - if (get_module().is_portal_block(event.getBlock())) { - event.setCancelled(true); - } - } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_block_break(final BlockBreakEvent event) { + // Prevent breaking of portal blocks + if (get_module().is_portal_block(event.getBlock())) { + event.setCancelled(true); + } + } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_block_place(final BlockPlaceEvent event) { - // Prevent (re-)placing of portal blocks - if (get_module().is_portal_block(event.getBlock())) { - event.setCancelled(true); - } - } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_block_place(final BlockPlaceEvent event) { + // Prevent (re-)placing of portal blocks + if (get_module().is_portal_block(event.getBlock())) { + event.setCancelled(true); + } + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_entity_explode(final EntityExplodeEvent event) { - // Prevent explosions from removing portal blocks - event.blockList().removeIf(block -> get_module().is_portal_block(block)); - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_entity_explode(final EntityExplodeEvent event) { + // Prevent explosions from removing portal blocks + event.blockList().removeIf(block -> get_module().is_portal_block(block)); + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_entity_change_block(final EntityChangeBlockEvent event) { - // Prevent entities from changing portal blocks - if (get_module().is_portal_block(event.getBlock())) { - event.setCancelled(true); - } - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_entity_change_block(final EntityChangeBlockEvent event) { + // Prevent entities from changing portal blocks + if (get_module().is_portal_block(event.getBlock())) { + event.setCancelled(true); + } + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_block_piston_extend(final BlockPistonExtendEvent event) { - // Prevent pistons from moving portal blocks - for (final var block : event.getBlocks()) { - if (get_module().is_portal_block(block)) { - event.setCancelled(true); - return; - } - } - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_block_piston_extend(final BlockPistonExtendEvent event) { + // Prevent pistons from moving portal blocks + for (final var block : event.getBlocks()) { + if (get_module().is_portal_block(block)) { + event.setCancelled(true); + return; + } + } + } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_block_piston_retract(final BlockPistonRetractEvent event) { - // Prevent pistons from moving portal blocks - for (final var block : event.getBlocks()) { - if (get_module().is_portal_block(block)) { - event.setCancelled(true); - return; - } - } - } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_block_piston_retract(final BlockPistonRetractEvent event) { + // Prevent pistons from moving portal blocks + for (final var block : event.getBlocks()) { + if (get_module().is_portal_block(block)) { + event.setCancelled(true); + return; + } + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayer.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayer.java index 13a605fde..48c134cab 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayer.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayer.java @@ -10,59 +10,59 @@ public class PortalBlueMapLayer extends ModuleComponent { - @ConfigBoolean(def = false, desc = "If the marker set should be hidden by default.") - public boolean config_hide_by_default; + @ConfigBoolean(def = false, desc = "If the marker set should be hidden by default.") + public boolean config_hide_by_default; - @LangMessage - public TranslatedMessage lang_layer_label; + @LangMessage + public TranslatedMessage lang_layer_label; - @LangMessage - public TranslatedMessage lang_marker_label; + @LangMessage + public TranslatedMessage lang_marker_label; - private PortalBlueMapLayerDelegate delegate = null; + private PortalBlueMapLayerDelegate delegate = null; - public PortalBlueMapLayer(final Context context) { - super(context.group("blue_map", "Enable BlueMap integration to show public portals.")); - } + public PortalBlueMapLayer(final Context context) { + super(context.group("blue_map", "Enable BlueMap integration to show public portals.")); + } - public void delayed_on_enable() { - final var plugin = get_module().getServer().getPluginManager().getPlugin("BlueMap"); - if (plugin == null) { - return; - } + public void delayed_on_enable() { + final var plugin = get_module().getServer().getPluginManager().getPlugin("BlueMap"); + if (plugin == null) { + return; + } - delegate = new PortalBlueMapLayerDelegate(this); - delegate.on_enable(plugin); - } + delegate = new PortalBlueMapLayerDelegate(this); + delegate.on_enable(plugin); + } - @Override - public void on_enable() { - schedule_next_tick(this::delayed_on_enable); - } + @Override + public void on_enable() { + schedule_next_tick(this::delayed_on_enable); + } - @Override - public void on_disable() { - if (delegate != null) { - delegate.on_disable(); - delegate = null; - } - } + @Override + public void on_disable() { + if (delegate != null) { + delegate.on_disable(); + delegate = null; + } + } - public void update_marker(final Portal portal) { - if (delegate != null) { - delegate.update_marker(portal); - } - } + public void update_marker(final Portal portal) { + if (delegate != null) { + delegate.update_marker(portal); + } + } - public void remove_marker(final UUID portal_id) { - if (delegate != null) { - delegate.remove_marker(portal_id); - } - } + public void remove_marker(final UUID portal_id) { + if (delegate != null) { + delegate.remove_marker(portal_id); + } + } - public void update_all_markers() { - if (delegate != null) { - delegate.update_all_markers(); - } - } + public void update_all_markers() { + if (delegate != null) { + delegate.update_all_markers(); + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayerDelegate.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayerDelegate.java index 42fae94ce..1cf85fde6 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayerDelegate.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalBlueMapLayerDelegate.java @@ -7,105 +7,107 @@ import de.bluecolored.bluemap.api.markers.MarkerSet; import java.util.HashMap; import java.util.UUID; - import org.bukkit.World; import org.bukkit.plugin.Plugin; import org.oddlama.vane.portals.portal.Portal; public class PortalBlueMapLayerDelegate { - public static final String MARKER_SET_ID = "vane_portals.portals"; - - private final PortalBlueMapLayer parent; - - private boolean bluemap_enabled = false; - - public PortalBlueMapLayerDelegate(final PortalBlueMapLayer parent) { - this.parent = parent; - } - - public Portals get_module() { - return parent.get_module(); - } - - public void on_enable(final Plugin plugin) { - BlueMapAPI.onEnable(api -> { - get_module().log.info("Enabling BlueMap integration"); - bluemap_enabled = true; - - // Create marker sets - for (final var world : get_module().getServer().getWorlds()) { - create_marker_set(api, world); - } - - update_all_markers(); - }); - } - - public void on_disable() { - if (!bluemap_enabled) { - return; - } - - get_module().log.info("Disabling BlueMap integration"); - bluemap_enabled = false; - } - - // world_id -> MarkerSet - private final HashMap marker_sets = new HashMap<>(); - private void create_marker_set(final BlueMapAPI api, final World world) { - if (marker_sets.containsKey(world.getUID())) { - return; - } - - final var marker_set = MarkerSet.builder() - .label(parent.lang_layer_label.str()) - .toggleable(true) - .defaultHidden(parent.config_hide_by_default) - .build(); - - api.getWorld(world).ifPresent(bm_world -> { - for (final var map : bm_world.getMaps()) { - map.getMarkerSets().put(MARKER_SET_ID, marker_set); - } - }); - - marker_sets.put(world.getUID(), marker_set); - } - - public void update_marker(final Portal portal) { - remove_marker(portal.id()); - - // Don't show private portals - if (portal.visibility() == Portal.Visibility.PRIVATE) { - return; - } - - final var loc = portal.spawn(); - final var marker = HtmlMarker.builder() - .position(loc.getX(), loc.getY(), loc.getZ()) - .label("Portal " + portal.name()) - .html(parent.lang_marker_label.str(escapeHtml(portal.name()))) - .build(); - - // Existing markers will be overwritten. - marker_sets.get(loc.getWorld().getUID()).getMarkers().put(portal.id().toString(), marker); - } - - public void remove_marker(final UUID portal_id) { - for (final var marker_set : marker_sets.values()) { - marker_set.getMarkers().remove(portal_id.toString()); - } - } - - public void update_all_markers() { - for (final var portal : get_module().all_available_portals()) { - // Don't show private portals - if (portal.visibility() == Portal.Visibility.PRIVATE) { - continue; - } - - update_marker(portal); - } - } + public static final String MARKER_SET_ID = "vane_portals.portals"; + + private final PortalBlueMapLayer parent; + + private boolean bluemap_enabled = false; + + public PortalBlueMapLayerDelegate(final PortalBlueMapLayer parent) { + this.parent = parent; + } + + public Portals get_module() { + return parent.get_module(); + } + + public void on_enable(final Plugin plugin) { + BlueMapAPI.onEnable(api -> { + get_module().log.info("Enabling BlueMap integration"); + bluemap_enabled = true; + + // Create marker sets + for (final var world : get_module().getServer().getWorlds()) { + create_marker_set(api, world); + } + + update_all_markers(); + }); + } + + public void on_disable() { + if (!bluemap_enabled) { + return; + } + + get_module().log.info("Disabling BlueMap integration"); + bluemap_enabled = false; + } + + // world_id -> MarkerSet + private final HashMap marker_sets = new HashMap<>(); + + private void create_marker_set(final BlueMapAPI api, final World world) { + if (marker_sets.containsKey(world.getUID())) { + return; + } + + final var marker_set = MarkerSet.builder() + .label(parent.lang_layer_label.str()) + .toggleable(true) + .defaultHidden(parent.config_hide_by_default) + .build(); + + api + .getWorld(world) + .ifPresent(bm_world -> { + for (final var map : bm_world.getMaps()) { + map.getMarkerSets().put(MARKER_SET_ID, marker_set); + } + }); + + marker_sets.put(world.getUID(), marker_set); + } + + public void update_marker(final Portal portal) { + remove_marker(portal.id()); + + // Don't show private portals + if (portal.visibility() == Portal.Visibility.PRIVATE) { + return; + } + + final var loc = portal.spawn(); + final var marker = HtmlMarker.builder() + .position(loc.getX(), loc.getY(), loc.getZ()) + .label("Portal " + portal.name()) + .html(parent.lang_marker_label.str(escapeHtml(portal.name()))) + .build(); + + // Existing markers will be overwritten. + marker_sets.get(loc.getWorld().getUID()).getMarkers().put(portal.id().toString(), marker); + } + + public void remove_marker(final UUID portal_id) { + for (final var marker_set : marker_sets.values()) { + marker_set.getMarkers().remove(portal_id.toString()); + } + } + + public void update_all_markers() { + for (final var portal : get_module().all_available_portals()) { + // Don't show private portals + if (portal.visibility() == Portal.Visibility.PRIVATE) { + continue; + } + + update_marker(portal); + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalConstructor.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalConstructor.java index cf7ad865a..675720bb4 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalConstructor.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalConstructor.java @@ -35,496 +35,496 @@ public class PortalConstructor extends Listener { - @ConfigMaterial(def = Material.ENCHANTING_TABLE, desc = "The block used to build portal consoles.") - public Material config_material_console; + @ConfigMaterial(def = Material.ENCHANTING_TABLE, desc = "The block used to build portal consoles.") + public Material config_material_console; - @ConfigMaterial(def = Material.OBSIDIAN, desc = "The block used to build the portal boundary. Variation 1.") - public Material config_material_boundary_1; + @ConfigMaterial(def = Material.OBSIDIAN, desc = "The block used to build the portal boundary. Variation 1.") + public Material config_material_boundary_1; - @ConfigMaterial(def = Material.CRYING_OBSIDIAN, desc = "The block used to build the portal boundary. Variation 2.") - public Material config_material_boundary_2; + @ConfigMaterial(def = Material.CRYING_OBSIDIAN, desc = "The block used to build the portal boundary. Variation 2.") + public Material config_material_boundary_2; - @ConfigMaterial(def = Material.GOLD_BLOCK, desc = "The block used to build the portal boundary. Variation 3.") - public Material config_material_boundary_3; + @ConfigMaterial(def = Material.GOLD_BLOCK, desc = "The block used to build the portal boundary. Variation 3.") + public Material config_material_boundary_3; - @ConfigMaterial( - def = Material.GILDED_BLACKSTONE, - desc = "The block used to build the portal boundary. Variation 4." - ) - public Material config_material_boundary_4; + @ConfigMaterial( + def = Material.GILDED_BLACKSTONE, + desc = "The block used to build the portal boundary. Variation 4." + ) + public Material config_material_boundary_4; - @ConfigMaterial(def = Material.EMERALD_BLOCK, desc = "The block used to build the portal boundary. Variation 5.") - public Material config_material_boundary_5; + @ConfigMaterial(def = Material.EMERALD_BLOCK, desc = "The block used to build the portal boundary. Variation 5.") + public Material config_material_boundary_5; - @ConfigMaterial(def = Material.NETHERITE_BLOCK, desc = "The block used to build the portal origin.") - public Material config_material_origin; + @ConfigMaterial(def = Material.NETHERITE_BLOCK, desc = "The block used to build the portal origin.") + public Material config_material_origin; - @ConfigMaterial(def = Material.AIR, desc = "The block used to build the portal area.") - public Material config_material_portal_area; + @ConfigMaterial(def = Material.AIR, desc = "The block used to build the portal area.") + public Material config_material_portal_area; - @ConfigInt(def = 12, min = 1, desc = "Maximum horizontal distance between a console block and the portal.") - public int config_console_max_distance_xz; + @ConfigInt(def = 12, min = 1, desc = "Maximum horizontal distance between a console block and the portal.") + public int config_console_max_distance_xz; - @ConfigInt(def = 12, min = 1, desc = "Maximum vertical distance between a console block and the portal.") - public int config_console_max_distance_y; + @ConfigInt(def = 12, min = 1, desc = "Maximum vertical distance between a console block and the portal.") + public int config_console_max_distance_y; - @ConfigInt( - def = 1024, - min = 256, - desc = "Maximum steps for the floodfill algorithm. This should only be increased if you want really big portals. It's recommended to keep this as low as possible." - ) - public int config_area_floodfill_max_steps = 1024; + @ConfigInt( + def = 1024, + min = 256, + desc = "Maximum steps for the floodfill algorithm. This should only be increased if you want really big portals. It's recommended to keep this as low as possible." + ) + public int config_area_floodfill_max_steps = 1024; - @ConfigInt(def = 24, min = 8, desc = "Maximum portal area width (bounding box will be measured).") - public int config_area_max_width; + @ConfigInt(def = 24, min = 8, desc = "Maximum portal area width (bounding box will be measured).") + public int config_area_max_width; - @ConfigInt(def = 24, min = 8, desc = "Maximum portal area height (bounding box will be measured).") - public int config_area_max_height = 24; + @ConfigInt(def = 24, min = 8, desc = "Maximum portal area height (bounding box will be measured).") + public int config_area_max_height = 24; - @ConfigInt(def = 128, min = 8, desc = "Maximum total amount of portal area blocks.") - public int config_area_max_blocks = 128; + @ConfigInt(def = 128, min = 8, desc = "Maximum total amount of portal area blocks.") + public int config_area_max_blocks = 128; - @LangMessage - public TranslatedMessage lang_select_boundary_now; + @LangMessage + public TranslatedMessage lang_select_boundary_now; - @LangMessage - public TranslatedMessage lang_console_invalid_type; + @LangMessage + public TranslatedMessage lang_console_invalid_type; - @LangMessage - public TranslatedMessage lang_console_different_world; + @LangMessage + public TranslatedMessage lang_console_different_world; - @LangMessage - public TranslatedMessage lang_console_too_far_away; + @LangMessage + public TranslatedMessage lang_console_too_far_away; - @LangMessage - public TranslatedMessage lang_console_linked; + @LangMessage + public TranslatedMessage lang_console_linked; - @LangMessage - public TranslatedMessage lang_no_boundary_found; + @LangMessage + public TranslatedMessage lang_no_boundary_found; - @LangMessage - public TranslatedMessage lang_no_origin; + @LangMessage + public TranslatedMessage lang_no_origin; - @LangMessage - public TranslatedMessage lang_multiple_origins; + @LangMessage + public TranslatedMessage lang_multiple_origins; - @LangMessage - public TranslatedMessage lang_no_portal_block_above_origin; + @LangMessage + public TranslatedMessage lang_no_portal_block_above_origin; - @LangMessage - public TranslatedMessage lang_not_enough_portal_blocks_above_origin; + @LangMessage + public TranslatedMessage lang_not_enough_portal_blocks_above_origin; - @LangMessage - public TranslatedMessage lang_too_large; - - @LangMessage - public TranslatedMessage lang_too_small_spawn; - - @LangMessage - public TranslatedMessage lang_too_many_portal_area_blocks; - - @LangMessage - public TranslatedMessage lang_portal_area_obstructed; - - @LangMessage - public TranslatedMessage lang_intersects_existing_portal; - - @LangMessage - public TranslatedMessage lang_build_restricted; - - @LangMessage - public TranslatedMessage lang_link_restricted; - - @LangMessage - public TranslatedMessage lang_target_already_connected; - - @LangMessage - public TranslatedMessage lang_source_use_restricted; - - @LangMessage - public TranslatedMessage lang_target_use_restricted; - - private Set portal_boundary_build_materials = new HashSet<>(); - - private HashMap pending_console = new HashMap<>(); - - public PortalConstructor(Context context) { - super(context); - } - - @Override - public void on_config_change() { - portal_boundary_build_materials.clear(); - portal_boundary_build_materials.add(config_material_boundary_1); - portal_boundary_build_materials.add(config_material_boundary_2); - portal_boundary_build_materials.add(config_material_boundary_3); - portal_boundary_build_materials.add(config_material_boundary_4); - portal_boundary_build_materials.add(config_material_boundary_5); - portal_boundary_build_materials.add(config_material_origin); - } - - public int max_dim_x(Plane plane) { - return plane.x() ? config_area_max_width : 1; - } - - public int max_dim_y(Plane plane) { - return plane.y() ? config_area_max_height : 1; - } - - public int max_dim_z(Plane plane) { - return plane.z() ? config_area_max_width : 1; - } - - private boolean remember_new_console(final Player player, final Block console_block) { - final var changed = !console_block.equals(pending_console.get(player.getUniqueId())); - // Add console_block as pending console - pending_console.put(player.getUniqueId(), console_block); - if (changed) { - lang_select_boundary_now.send(player); - } - return changed; - } - - private boolean can_link_console( - final Player player, - final PortalBoundary boundary, - final Block console, - boolean check_only - ) { - return can_link_console(player, boundary.all_blocks(), console, null, check_only); - } - - private boolean can_link_console( - final Player player, - final Portal portal, - final Block console, - boolean check_only - ) { - // Gather all portal blocks that aren't consoles - final var blocks = portal - .blocks() - .stream() - .filter(pb -> pb.type() != PortalBlock.Type.CONSOLE) - .map(pb -> pb.block()) - .collect(Collectors.toList()); - return can_link_console(player, blocks, console, portal, check_only); - } - - private boolean can_link_console( - final Player player, - final List blocks, - final Block console, - @Nullable final Portal existing_portal, - boolean check_only - ) { - // Check a console block type - if (console.getType() != config_material_console) { - lang_console_invalid_type.send(player); - return false; - } - - // Check world - if (!console.getWorld().equals(blocks.get(0).getWorld())) { - lang_console_different_world.send(player); - return false; - } - - // Check distance - boolean found_valid_block = false; - for (final var block : blocks) { - if ( - Math.abs(console.getX() - block.getX()) <= config_console_max_distance_xz && - Math.abs(console.getY() - block.getY()) <= config_console_max_distance_y && - Math.abs(console.getZ() - block.getZ()) <= config_console_max_distance_xz - ) { - found_valid_block = true; - break; - } - } - - if (!found_valid_block) { - lang_console_too_far_away.send(player); - return false; - } - - // Call event - final var event = new PortalLinkConsoleEvent(player, console, blocks, check_only, existing_portal); - get_module().getServer().getPluginManager().callEvent(event); - if (event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - lang_link_restricted.send(player); - return false; - } - - return true; - } - - private boolean link_console(final Player player, final Block console, final Portal portal) { - if (!can_link_console(player, portal, console, false)) { - return false; - } - - // Add portal block - get_module().add_new_portal_block(portal, create_portal_block(console)); - - // Update block blocks - portal.update_blocks(get_module()); - return true; - } - - private PortalBoundary find_boundary(final Player player, final Block block) { - final var boundary = PortalBoundary.search_at(this, block); - if (boundary == null) { - lang_no_boundary_found.send(player); - return null; - } - - // Check for error - switch (boundary.error_state()) { - case NONE: - /* The Boundary is fine */break; - case NO_ORIGIN: - lang_no_origin.send(player); - return null; - case MULTIPLE_ORIGINS: - lang_multiple_origins.send(player); - return null; - case NO_PORTAL_BLOCK_ABOVE_ORIGIN: - lang_no_portal_block_above_origin.send(player); - return null; - case NOT_ENOUGH_PORTAL_BLOCKS_ABOVE_ORIGIN: - lang_not_enough_portal_blocks_above_origin.send(player); - return null; - case TOO_LARGE_X: - lang_too_large.send(player, "§6x"); - return null; - case TOO_LARGE_Y: - lang_too_large.send(player, "§6y"); - return null; - case TOO_LARGE_Z: - lang_too_large.send(player, "§6z"); - return null; - case TOO_SMALL_SPAWN_X: - lang_too_small_spawn.send(player, "§6x"); - return null; - case TOO_SMALL_SPAWN_Y: - lang_too_small_spawn.send(player, "§6y"); - return null; - case TOO_SMALL_SPAWN_Z: - lang_too_small_spawn.send(player, "§6z"); - return null; - case PORTAL_AREA_OBSTRUCTED: - lang_portal_area_obstructed.send(player); - return null; - case TOO_MANY_PORTAL_AREA_BLOCKS: - lang_too_many_portal_area_blocks.send( - player, - "§6" + boundary.portal_area_blocks().size(), - "§6" + config_area_max_blocks - ); - return null; - } - - if (boundary.intersects_existing_portal(this)) { - lang_intersects_existing_portal.send(player); - return null; - } - - return boundary; - } - - public boolean is_type_part_of_boundary(final Material material) { - return ( - material == config_material_boundary_1 || - material == config_material_boundary_2 || - material == config_material_boundary_3 || - material == config_material_boundary_4 || - material == config_material_boundary_5 - ); - } - - public boolean is_type_part_of_boundary_or_origin(final Material material) { - return material == config_material_origin || is_type_part_of_boundary(material); - } - - private PortalBoundary check_construction_conditions( - final Player player, - final Block console, - final Block boundary_block, - boolean check_only - ) { - if (get_module().is_portal_block(boundary_block)) { - get_module() - .log.severe( - "construct_portal() was called on a boundary that already belongs to a portal! This is a bug." - ); - return null; - } - - // Search for valid portal boundary - final var boundary = find_boundary(player, boundary_block); - if (boundary == null) { - return null; - } - - // Check portal construct event - final var event = new PortalConstructEvent(player, boundary, check_only); - get_module().getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - lang_build_restricted.send(player); - return null; - } - - // Check console distance and build permission - if (!can_link_console(player, boundary, console, true)) { - return null; - } - - return boundary; - } - - private PortalBlock create_portal_block(final Block block) { - final PortalBlock.Type type; - var mat = block.getType(); - // treat cave air and void air as normal air - if(mat == Material.CAVE_AIR || mat == Material.VOID_AIR) { - mat = Material.AIR; - } - if (mat == config_material_console) { - type = PortalBlock.Type.CONSOLE; - } else if (mat == config_material_boundary_1) { - type = PortalBlock.Type.BOUNDARY_1; - } else if (mat == config_material_boundary_2) { - type = PortalBlock.Type.BOUNDARY_2; - } else if (mat == config_material_boundary_3) { - type = PortalBlock.Type.BOUNDARY_3; - } else if (mat == config_material_boundary_4) { - type = PortalBlock.Type.BOUNDARY_4; - } else if (mat == config_material_boundary_5) { - type = PortalBlock.Type.BOUNDARY_5; - } else if (mat == config_material_origin) { - type = PortalBlock.Type.ORIGIN; - } else if (mat == config_material_portal_area) { - type = PortalBlock.Type.PORTAL; - } else { - get_module() - .log.warning( - "Invalid block type '" + mat + "' encountered in portal block creation. Assuming boundary variant 1." - ); - type = PortalBlock.Type.BOUNDARY_1; - } - return new PortalBlock(block, type); - } - - private boolean construct_portal(final Player player, final Block console, final Block boundary_block) { - if (check_construction_conditions(player, console, boundary_block, true) == null) { - return false; - } - - // Show name chooser - get_module() - .menus.enter_name_menu.create( - player, - (p, name) -> { - // Re-check conditions, as someone could have changed blocks. This prevents this race condition. - final var boundary = check_construction_conditions(p, console, boundary_block, false); - if (boundary == null) { - return ClickResult.ERROR; - } - - // Determine orientation - final var orientation = Orientation.from( - boundary.plane(), - boundary.origin_block(), - console, - player.getLocation() - ); - - // Construct portal - final var portal = new Portal(p.getUniqueId(), orientation, boundary.spawn()); - portal.name(name); - get_module().add_new_portal(portal); - - // Add portal blocks - for (final var block : boundary.all_blocks()) { - get_module().add_new_portal_block(portal, create_portal_block(block)); - } - - // Link console - link_console(p, console, portal); - - // Force update storage now, as a precaution. - get_module().update_persistent_data(); - - // Update portal blocks once - portal.update_blocks(get_module()); - return ClickResult.SUCCESS; - } - ) - .open(player); - - return true; - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on_player_interact_console(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - final var block = event.getClickedBlock(); - if (block.getType() != config_material_console) { - return; - } - - // Abort if the console belongs to another portal already. - if (get_module().is_portal_block(block)) { - return; - } - - // TODO portal stone as item instead of shifting? - // Only if player sneak-right-clicks the console - final var player = event.getPlayer(); - if (!player.isSneaking() || event.getHand() != EquipmentSlot.HAND) { - return; - } - - remember_new_console(player, block); - swing_arm(player, event.getHand()); - event.setUseInteractedBlock(Event.Result.DENY); - event.setUseItemInHand(Event.Result.DENY); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = false) - public void on_player_interact_boundary(final PlayerInteractEvent event) { - if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - final var block = event.getClickedBlock(); - final var portal = get_module().portal_for(block); - final var type = block.getType(); - if (portal == null && !portal_boundary_build_materials.contains(type)) { - return; - } - - // Break if no console is pending - final var player = event.getPlayer(); - final var console = pending_console.remove(player.getUniqueId()); - if (console == null) { - return; - } - - if (portal == null) { - if (construct_portal(player, console, block)) { - swing_arm(player, event.getHand()); - } - } else { - if (link_console(player, console, portal)) { - swing_arm(player, event.getHand()); - } - } - - event.setUseInteractedBlock(Event.Result.DENY); - event.setUseItemInHand(Event.Result.DENY); - } + @LangMessage + public TranslatedMessage lang_too_large; + + @LangMessage + public TranslatedMessage lang_too_small_spawn; + + @LangMessage + public TranslatedMessage lang_too_many_portal_area_blocks; + + @LangMessage + public TranslatedMessage lang_portal_area_obstructed; + + @LangMessage + public TranslatedMessage lang_intersects_existing_portal; + + @LangMessage + public TranslatedMessage lang_build_restricted; + + @LangMessage + public TranslatedMessage lang_link_restricted; + + @LangMessage + public TranslatedMessage lang_target_already_connected; + + @LangMessage + public TranslatedMessage lang_source_use_restricted; + + @LangMessage + public TranslatedMessage lang_target_use_restricted; + + private Set portal_boundary_build_materials = new HashSet<>(); + + private HashMap pending_console = new HashMap<>(); + + public PortalConstructor(Context context) { + super(context); + } + + @Override + public void on_config_change() { + portal_boundary_build_materials.clear(); + portal_boundary_build_materials.add(config_material_boundary_1); + portal_boundary_build_materials.add(config_material_boundary_2); + portal_boundary_build_materials.add(config_material_boundary_3); + portal_boundary_build_materials.add(config_material_boundary_4); + portal_boundary_build_materials.add(config_material_boundary_5); + portal_boundary_build_materials.add(config_material_origin); + } + + public int max_dim_x(Plane plane) { + return plane.x() ? config_area_max_width : 1; + } + + public int max_dim_y(Plane plane) { + return plane.y() ? config_area_max_height : 1; + } + + public int max_dim_z(Plane plane) { + return plane.z() ? config_area_max_width : 1; + } + + private boolean remember_new_console(final Player player, final Block console_block) { + final var changed = !console_block.equals(pending_console.get(player.getUniqueId())); + // Add console_block as pending console + pending_console.put(player.getUniqueId(), console_block); + if (changed) { + lang_select_boundary_now.send(player); + } + return changed; + } + + private boolean can_link_console( + final Player player, + final PortalBoundary boundary, + final Block console, + boolean check_only + ) { + return can_link_console(player, boundary.all_blocks(), console, null, check_only); + } + + private boolean can_link_console( + final Player player, + final Portal portal, + final Block console, + boolean check_only + ) { + // Gather all portal blocks that aren't consoles + final var blocks = portal + .blocks() + .stream() + .filter(pb -> pb.type() != PortalBlock.Type.CONSOLE) + .map(pb -> pb.block()) + .collect(Collectors.toList()); + return can_link_console(player, blocks, console, portal, check_only); + } + + private boolean can_link_console( + final Player player, + final List blocks, + final Block console, + @Nullable final Portal existing_portal, + boolean check_only + ) { + // Check a console block type + if (console.getType() != config_material_console) { + lang_console_invalid_type.send(player); + return false; + } + + // Check world + if (!console.getWorld().equals(blocks.get(0).getWorld())) { + lang_console_different_world.send(player); + return false; + } + + // Check distance + boolean found_valid_block = false; + for (final var block : blocks) { + if ( + Math.abs(console.getX() - block.getX()) <= config_console_max_distance_xz && + Math.abs(console.getY() - block.getY()) <= config_console_max_distance_y && + Math.abs(console.getZ() - block.getZ()) <= config_console_max_distance_xz + ) { + found_valid_block = true; + break; + } + } + + if (!found_valid_block) { + lang_console_too_far_away.send(player); + return false; + } + + // Call event + final var event = new PortalLinkConsoleEvent(player, console, blocks, check_only, existing_portal); + get_module().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { + lang_link_restricted.send(player); + return false; + } + + return true; + } + + private boolean link_console(final Player player, final Block console, final Portal portal) { + if (!can_link_console(player, portal, console, false)) { + return false; + } + + // Add portal block + get_module().add_new_portal_block(portal, create_portal_block(console)); + + // Update block blocks + portal.update_blocks(get_module()); + return true; + } + + private PortalBoundary find_boundary(final Player player, final Block block) { + final var boundary = PortalBoundary.search_at(this, block); + if (boundary == null) { + lang_no_boundary_found.send(player); + return null; + } + + // Check for error + switch (boundary.error_state()) { + case NONE: + /* The Boundary is fine */break; + case NO_ORIGIN: + lang_no_origin.send(player); + return null; + case MULTIPLE_ORIGINS: + lang_multiple_origins.send(player); + return null; + case NO_PORTAL_BLOCK_ABOVE_ORIGIN: + lang_no_portal_block_above_origin.send(player); + return null; + case NOT_ENOUGH_PORTAL_BLOCKS_ABOVE_ORIGIN: + lang_not_enough_portal_blocks_above_origin.send(player); + return null; + case TOO_LARGE_X: + lang_too_large.send(player, "§6x"); + return null; + case TOO_LARGE_Y: + lang_too_large.send(player, "§6y"); + return null; + case TOO_LARGE_Z: + lang_too_large.send(player, "§6z"); + return null; + case TOO_SMALL_SPAWN_X: + lang_too_small_spawn.send(player, "§6x"); + return null; + case TOO_SMALL_SPAWN_Y: + lang_too_small_spawn.send(player, "§6y"); + return null; + case TOO_SMALL_SPAWN_Z: + lang_too_small_spawn.send(player, "§6z"); + return null; + case PORTAL_AREA_OBSTRUCTED: + lang_portal_area_obstructed.send(player); + return null; + case TOO_MANY_PORTAL_AREA_BLOCKS: + lang_too_many_portal_area_blocks.send( + player, + "§6" + boundary.portal_area_blocks().size(), + "§6" + config_area_max_blocks + ); + return null; + } + + if (boundary.intersects_existing_portal(this)) { + lang_intersects_existing_portal.send(player); + return null; + } + + return boundary; + } + + public boolean is_type_part_of_boundary(final Material material) { + return ( + material == config_material_boundary_1 || + material == config_material_boundary_2 || + material == config_material_boundary_3 || + material == config_material_boundary_4 || + material == config_material_boundary_5 + ); + } + + public boolean is_type_part_of_boundary_or_origin(final Material material) { + return material == config_material_origin || is_type_part_of_boundary(material); + } + + private PortalBoundary check_construction_conditions( + final Player player, + final Block console, + final Block boundary_block, + boolean check_only + ) { + if (get_module().is_portal_block(boundary_block)) { + get_module() + .log.severe( + "construct_portal() was called on a boundary that already belongs to a portal! This is a bug." + ); + return null; + } + + // Search for valid portal boundary + final var boundary = find_boundary(player, boundary_block); + if (boundary == null) { + return null; + } + + // Check portal construct event + final var event = new PortalConstructEvent(player, boundary, check_only); + get_module().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + lang_build_restricted.send(player); + return null; + } + + // Check console distance and build permission + if (!can_link_console(player, boundary, console, true)) { + return null; + } + + return boundary; + } + + private PortalBlock create_portal_block(final Block block) { + final PortalBlock.Type type; + var mat = block.getType(); + // treat cave air and void air as normal air + if (mat == Material.CAVE_AIR || mat == Material.VOID_AIR) { + mat = Material.AIR; + } + if (mat == config_material_console) { + type = PortalBlock.Type.CONSOLE; + } else if (mat == config_material_boundary_1) { + type = PortalBlock.Type.BOUNDARY_1; + } else if (mat == config_material_boundary_2) { + type = PortalBlock.Type.BOUNDARY_2; + } else if (mat == config_material_boundary_3) { + type = PortalBlock.Type.BOUNDARY_3; + } else if (mat == config_material_boundary_4) { + type = PortalBlock.Type.BOUNDARY_4; + } else if (mat == config_material_boundary_5) { + type = PortalBlock.Type.BOUNDARY_5; + } else if (mat == config_material_origin) { + type = PortalBlock.Type.ORIGIN; + } else if (mat == config_material_portal_area) { + type = PortalBlock.Type.PORTAL; + } else { + get_module() + .log.warning( + "Invalid block type '" + + mat + + "' encountered in portal block creation. Assuming boundary variant 1." + ); + type = PortalBlock.Type.BOUNDARY_1; + } + return new PortalBlock(block, type); + } + + private boolean construct_portal(final Player player, final Block console, final Block boundary_block) { + if (check_construction_conditions(player, console, boundary_block, true) == null) { + return false; + } + + // Show name chooser + get_module() + .menus.enter_name_menu.create(player, (p, name) -> { + // Re-check conditions, as someone could have changed blocks. This + // prevents this race condition. + final var boundary = check_construction_conditions(p, console, boundary_block, false); + if (boundary == null) { + return ClickResult.ERROR; + } + + // Determine orientation + final var orientation = Orientation.from( + boundary.plane(), + boundary.origin_block(), + console, + player.getLocation() + ); + + // Construct portal + final var portal = new Portal(p.getUniqueId(), orientation, boundary.spawn()); + portal.name(name); + get_module().add_new_portal(portal); + + // Add portal blocks + for (final var block : boundary.all_blocks()) { + get_module().add_new_portal_block(portal, create_portal_block(block)); + } + + // Link console + link_console(p, console, portal); + + // Force update storage now, as a precaution. + get_module().update_persistent_data(); + + // Update portal blocks once + portal.update_blocks(get_module()); + return ClickResult.SUCCESS; + }) + .open(player); + + return true; + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on_player_interact_console(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + final var block = event.getClickedBlock(); + if (block.getType() != config_material_console) { + return; + } + + // Abort if the console belongs to another portal already. + if (get_module().is_portal_block(block)) { + return; + } + + // TODO portal stone as item instead of shifting? + // Only if player sneak-right-clicks the console + final var player = event.getPlayer(); + if (!player.isSneaking() || event.getHand() != EquipmentSlot.HAND) { + return; + } + + remember_new_console(player, block); + swing_arm(player, event.getHand()); + event.setUseInteractedBlock(Event.Result.DENY); + event.setUseItemInHand(Event.Result.DENY); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = false) + public void on_player_interact_boundary(final PlayerInteractEvent event) { + if (!event.hasBlock() || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + final var block = event.getClickedBlock(); + final var portal = get_module().portal_for(block); + final var type = block.getType(); + if (portal == null && !portal_boundary_build_materials.contains(type)) { + return; + } + + // Break if no console is pending + final var player = event.getPlayer(); + final var console = pending_console.remove(player.getUniqueId()); + if (console == null) { + return; + } + + if (portal == null) { + if (construct_portal(player, console, block)) { + swing_arm(player, event.getHand()); + } + } else { + if (link_console(player, console, portal)) { + swing_arm(player, event.getHand()); + } + } + + event.setUseInteractedBlock(Event.Result.DENY); + event.setUseItemInHand(Event.Result.DENY); + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayer.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayer.java index 69da21312..ab42b0ee9 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayer.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayer.java @@ -12,72 +12,72 @@ public class PortalDynmapLayer extends ModuleComponent { - public static final String LAYER_ID = "vane_portals.portals"; - - @ConfigInt(def = 29, min = 0, desc = "Layer ordering priority.") - public int config_layer_priority; - - @ConfigBoolean(def = false, desc = "If the layer should be hidden by default.") - public boolean config_layer_hide; - - @ConfigString(def = "compass", desc = "The dynmap marker icon.") - public String config_marker_icon; - - @LangMessage - public TranslatedMessage lang_layer_label; - - @LangMessage - public TranslatedMessage lang_marker_label; - - private PortalDynmapLayerDelegate delegate = null; - - public PortalDynmapLayer(final Context context) { - super( - context.group( - "dynmap", - "Enable dynmap integration. Public portals will then be shown on a separate dynmap layer." - ) - ); - } - - public void delayed_on_enable() { - final var plugin = get_module().getServer().getPluginManager().getPlugin("dynmap"); - if (plugin == null) { - return; - } - - delegate = new PortalDynmapLayerDelegate(this); - delegate.on_enable(plugin); - } - - @Override - public void on_enable() { - schedule_next_tick(this::delayed_on_enable); - } - - @Override - public void on_disable() { - if (delegate != null) { - delegate.on_disable(); - delegate = null; - } - } - - public void update_marker(final Portal portal) { - if (delegate != null) { - delegate.update_marker(portal); - } - } - - public void remove_marker(final UUID portal_id) { - if (delegate != null) { - delegate.remove_marker(portal_id); - } - } - - public void update_all_markers() { - if (delegate != null) { - delegate.update_all_markers(); - } - } + public static final String LAYER_ID = "vane_portals.portals"; + + @ConfigInt(def = 29, min = 0, desc = "Layer ordering priority.") + public int config_layer_priority; + + @ConfigBoolean(def = false, desc = "If the layer should be hidden by default.") + public boolean config_layer_hide; + + @ConfigString(def = "compass", desc = "The dynmap marker icon.") + public String config_marker_icon; + + @LangMessage + public TranslatedMessage lang_layer_label; + + @LangMessage + public TranslatedMessage lang_marker_label; + + private PortalDynmapLayerDelegate delegate = null; + + public PortalDynmapLayer(final Context context) { + super( + context.group( + "dynmap", + "Enable dynmap integration. Public portals will then be shown on a separate dynmap layer." + ) + ); + } + + public void delayed_on_enable() { + final var plugin = get_module().getServer().getPluginManager().getPlugin("dynmap"); + if (plugin == null) { + return; + } + + delegate = new PortalDynmapLayerDelegate(this); + delegate.on_enable(plugin); + } + + @Override + public void on_enable() { + schedule_next_tick(this::delayed_on_enable); + } + + @Override + public void on_disable() { + if (delegate != null) { + delegate.on_disable(); + delegate = null; + } + } + + public void update_marker(final Portal portal) { + if (delegate != null) { + delegate.update_marker(portal); + } + } + + public void remove_marker(final UUID portal_id) { + if (delegate != null) { + delegate.remove_marker(portal_id); + } + } + + public void update_all_markers() { + if (delegate != null) { + delegate.update_all_markers(); + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayerDelegate.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayerDelegate.java index 41385ceb3..05839f016 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayerDelegate.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalDynmapLayerDelegate.java @@ -3,7 +3,6 @@ import java.util.HashSet; import java.util.UUID; import java.util.logging.Level; - import org.bukkit.plugin.Plugin; import org.dynmap.DynmapCommonAPI; import org.dynmap.DynmapCommonAPIListener; @@ -15,164 +14,166 @@ public class PortalDynmapLayerDelegate { - private final PortalDynmapLayer parent; - - private DynmapCommonAPI dynmap_api = null; - private MarkerAPI marker_api = null; - private boolean dynmap_enabled = false; - - private MarkerSet marker_set = null; - private MarkerIcon marker_icon = null; - - public PortalDynmapLayerDelegate(final PortalDynmapLayer parent) { - this.parent = parent; - } - - public Portals get_module() { - return parent.get_module(); - } - - public void on_enable(final Plugin plugin) { - try { - DynmapCommonAPIListener.register(new DynmapCommonAPIListener() { - - @Override - public void apiEnabled(DynmapCommonAPI api) { - dynmap_api = api; - marker_api = dynmap_api.getMarkerAPI(); - - } - - }); - - } catch (Exception e) { - get_module().log.log(Level.WARNING, "Error while enabling dynmap integration!", e); - return; - } - - if (marker_api == null) { - return; - } - - get_module().log.info("Enabling dynmap integration"); - dynmap_enabled = true; - create_or_load_layer(); - } - - public void on_disable() { - if (!dynmap_enabled) { - return; - } - - get_module().log.info("Disabling dynmap integration"); - dynmap_enabled = false; - dynmap_api = null; - marker_api = null; - } - - private void create_or_load_layer() { - // Create or retrieve layer - marker_set = marker_api.getMarkerSet(PortalDynmapLayer.LAYER_ID); - if (marker_set == null) { - marker_set = - marker_api.createMarkerSet(PortalDynmapLayer.LAYER_ID, parent.lang_layer_label.str(), null, false); - } - - if (marker_set == null) { - get_module().log.severe("Failed to create dynmap portal marker set!"); - return; - } - - // Update attributes - marker_set.setMarkerSetLabel(parent.lang_layer_label.str()); - marker_set.setLayerPriority(parent.config_layer_priority); - marker_set.setHideByDefault(parent.config_layer_hide); - - // Load marker - marker_icon = marker_api.getMarkerIcon(parent.config_marker_icon); - if (marker_icon == null) { - get_module().log.severe("Failed to load dynmap portal marker icon!"); - return; - } - - // Initial update - update_all_markers(); - } - - private String id_for(final UUID portal_id) { - return portal_id.toString(); - } - - private String id_for(final Portal portal) { - return id_for(portal.id()); - } - - public void update_marker(final Portal portal) { - if (!dynmap_enabled) { - return; - } - - // Don't show private portals - if (portal.visibility() == Portal.Visibility.PRIVATE) { - remove_marker(portal.id()); - return; - } - - final var loc = portal.spawn(); - final var world_name = loc.getWorld().getName(); - final var marker_id = id_for(portal); - final var marker_label = parent.lang_marker_label.str(portal.name()); - - marker_set.createMarker( - marker_id, - marker_label, - world_name, - loc.getX(), - loc.getY(), - loc.getZ(), - marker_icon, - false - ); - } - - public void remove_marker(final UUID portal_id) { - remove_marker(id_for(portal_id)); - } - - public void remove_marker(final String marker_id) { - if (!dynmap_enabled || marker_id == null) { - return; - } - - remove_marker(marker_set.findMarker(marker_id)); - } - - public void remove_marker(final Marker marker) { - if (!dynmap_enabled || marker == null) { - return; - } - - marker.deleteMarker(); - } - - public void update_all_markers() { - if (!dynmap_enabled) { - return; - } - - // Update all existing - final var id_set = new HashSet(); - for (final var portal : get_module().all_available_portals()) { - id_set.add(id_for(portal)); - update_marker(portal); - } - - // Remove orphaned - for (final var marker : marker_set.getMarkers()) { - final var id = marker.getMarkerID(); - if (id != null && !id_set.contains(id)) { - remove_marker(marker); - } - } - } + private final PortalDynmapLayer parent; + + private DynmapCommonAPI dynmap_api = null; + private MarkerAPI marker_api = null; + private boolean dynmap_enabled = false; + + private MarkerSet marker_set = null; + private MarkerIcon marker_icon = null; + + public PortalDynmapLayerDelegate(final PortalDynmapLayer parent) { + this.parent = parent; + } + + public Portals get_module() { + return parent.get_module(); + } + + public void on_enable(final Plugin plugin) { + try { + DynmapCommonAPIListener.register( + new DynmapCommonAPIListener() { + @Override + public void apiEnabled(DynmapCommonAPI api) { + dynmap_api = api; + marker_api = dynmap_api.getMarkerAPI(); + } + } + ); + } catch (Exception e) { + get_module().log.log(Level.WARNING, "Error while enabling dynmap integration!", e); + return; + } + + if (marker_api == null) { + return; + } + + get_module().log.info("Enabling dynmap integration"); + dynmap_enabled = true; + create_or_load_layer(); + } + + public void on_disable() { + if (!dynmap_enabled) { + return; + } + + get_module().log.info("Disabling dynmap integration"); + dynmap_enabled = false; + dynmap_api = null; + marker_api = null; + } + + private void create_or_load_layer() { + // Create or retrieve layer + marker_set = marker_api.getMarkerSet(PortalDynmapLayer.LAYER_ID); + if (marker_set == null) { + marker_set = marker_api.createMarkerSet( + PortalDynmapLayer.LAYER_ID, + parent.lang_layer_label.str(), + null, + false + ); + } + + if (marker_set == null) { + get_module().log.severe("Failed to create dynmap portal marker set!"); + return; + } + + // Update attributes + marker_set.setMarkerSetLabel(parent.lang_layer_label.str()); + marker_set.setLayerPriority(parent.config_layer_priority); + marker_set.setHideByDefault(parent.config_layer_hide); + + // Load marker + marker_icon = marker_api.getMarkerIcon(parent.config_marker_icon); + if (marker_icon == null) { + get_module().log.severe("Failed to load dynmap portal marker icon!"); + return; + } + + // Initial update + update_all_markers(); + } + + private String id_for(final UUID portal_id) { + return portal_id.toString(); + } + + private String id_for(final Portal portal) { + return id_for(portal.id()); + } + + public void update_marker(final Portal portal) { + if (!dynmap_enabled) { + return; + } + + // Don't show private portals + if (portal.visibility() == Portal.Visibility.PRIVATE) { + remove_marker(portal.id()); + return; + } + + final var loc = portal.spawn(); + final var world_name = loc.getWorld().getName(); + final var marker_id = id_for(portal); + final var marker_label = parent.lang_marker_label.str(portal.name()); + + marker_set.createMarker( + marker_id, + marker_label, + world_name, + loc.getX(), + loc.getY(), + loc.getZ(), + marker_icon, + false + ); + } + + public void remove_marker(final UUID portal_id) { + remove_marker(id_for(portal_id)); + } + + public void remove_marker(final String marker_id) { + if (!dynmap_enabled || marker_id == null) { + return; + } + + remove_marker(marker_set.findMarker(marker_id)); + } + + public void remove_marker(final Marker marker) { + if (!dynmap_enabled || marker == null) { + return; + } + + marker.deleteMarker(); + } + + public void update_all_markers() { + if (!dynmap_enabled) { + return; + } + + // Update all existing + final var id_set = new HashSet(); + for (final var portal : get_module().all_available_portals()) { + id_set.add(id_for(portal)); + update_marker(portal); + } + + // Remove orphaned + for (final var marker : marker_set.getMarkers()) { + final var id = marker.getMarkerID(); + if (id != null && !id_set.contains(id)) { + remove_marker(marker); + } + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalTeleporter.java b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalTeleporter.java index 9e6b333e5..6dfc05a3b 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/PortalTeleporter.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/PortalTeleporter.java @@ -2,11 +2,9 @@ import com.destroystokyo.paper.event.entity.EntityTeleportEndGatewayEvent; import com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent; - import java.util.ArrayList; import java.util.HashMap; import java.util.UUID; - import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; @@ -17,174 +15,201 @@ import org.bukkit.event.player.PlayerPortalEvent; import org.bukkit.util.Vector; import org.oddlama.vane.core.Listener; -import org.oddlama.vane.portals.event.EntityMoveEvent; import org.oddlama.vane.core.module.Context; +import org.oddlama.vane.portals.event.EntityMoveEvent; import org.oddlama.vane.portals.portal.Portal; import org.oddlama.vane.util.Nms; public class PortalTeleporter extends Listener { - private final HashMap entities_portalling = new HashMap<>(); - - public PortalTeleporter(Context context) { - super(context); - } - - private boolean cancel_portal_event(final Entity entity) { - if (entities_portalling.containsKey(entity.getUniqueId())) { - return true; - } - - return get_module().is_portal_block(entity.getLocation().getBlock()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_player_portal(final PlayerPortalEvent event) { - if (cancel_portal_event(event.getPlayer())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_entity_teleport_event(final EntityTeleportEvent event) { - if (cancel_portal_event(event.getEntity())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on_entity_teleport_end_gateway_event(final EntityTeleportEndGatewayEvent event) { - // End gateway teleport can be initiated when the bounding boxes overlap, so - // the entity location will not necessarily be at the position where the end gateway block is. - // Therefore, we additionally check whether the initiating end gateway block is part of a portal structure. - // Otherwise, this event would already be handled by EntityTeleportEvent. - if (get_module().is_portal_block(event.getGateway().getBlock())) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_player_teleport_event(final PlayerTeleportEndGatewayEvent event) { - final var block = event.getGateway().getBlock(); - if (get_module().is_portal_block(block)) { - event.setCancelled(true); - } - } - - private void teleport_single_entity(final Entity entity, final Location target_location, final Vector new_velocity) { - final var entity_location = entity.getLocation(); - final var nms_entity = Nms.entity_handle(entity); - - // Now teleport. There are many ways to do it, but some are preferred over others. - // Primarily, entity.teleport() will dismount any passengers. Meh. - if (target_location.getWorld() == entity_location.getWorld()) { - if (entity instanceof Player player) { - // For players traveling in the same world, we can use the NMS player's connection's - // teleport method, which only modifies player position without dismounting. - Nms.get_player(player).connection.teleport(target_location.getX(), target_location.getY(), target_location.getZ(), target_location.getYaw(), target_location.getPitch()); - } else { - // Similarly, we can just move entities. - nms_entity.absMoveTo(target_location.getX(), target_location.getY(), target_location.getZ(), target_location.getYaw(), target_location.getPitch()); - } - - // For some unknown reason (SPIGOT-619) we always need to set the yaw again. - nms_entity.setYHeadRot(target_location.getYaw()); - } else { - final var passengers = new ArrayList(entity.getPassengers()); - - // Entities traveling to a different dimension need to be despawned and respawned as both worlds are distinct levels. - // This means they must be dismounted (or unridden) before teleportation. - passengers.stream().forEach(entity::removePassenger); - entity.teleport(target_location); - - for (var p : passengers) { - teleport_single_entity(p, target_location, new Vector()); - entity.addPassenger(p); - } - } - - // Retain velocity. Previously we needed to force-set it in the next tick, - // as apparently the movement event overrides might override the velocity. - // Now we are using our own movement events which are run outside any - // entity ticking, so no such workaround is necessary. - //schedule_next_tick(() -> { - //entity.setVelocity(new_velocity); - //}); - entity.setVelocity(new_velocity); - } - - private void teleport_entity(final Entity entity, final Portal source, Portal target) { - var target_location = target.spawn().clone(); - if (entity instanceof LivingEntity living_entity) { - // Entities in vehicles are teleported when the vehicle is teleported. - if (living_entity.isInsideVehicle()) { - return; - } - - - // Increase Y value if an entity is currently flying through a portal that - // has extent in the y direction (i.e., is built upright) - if (living_entity.isGliding() && source.orientation().plane().y()) { - target_location.setY(target_location.getY() + 1.5); - } - } - - // Put null to signal initiated teleportation - final var entity_id = entity.getUniqueId(); - entities_portalling.put(entity_id, null); - - // First copy pitch & yaw to target, will be transformed soon. - final var entity_location = entity.getLocation(); - target_location.setPitch(entity_location.getPitch()); - target_location.setYaw(entity_location.getYaw()); - - // If the exit orientation of the target portal is locked, we make sure that - // the orientation of the entered portal is flipped if an entity (player) enters from the back. - // We have to flip the source portal orientation if the vector to be transformed - // is NOT opposing the source portal vector (i.e., not pointing against the front). - // Calculate new location (pitch, yaw) and velocity. - final var source_orientation = source.orientation(); - target_location = source_orientation.apply(target.orientation(), target_location, target.exit_orientation_locked()); - final var new_velocity = source_orientation.apply(target.orientation(), entity.getVelocity(), target.exit_orientation_locked()); - - teleport_single_entity(entity, target_location, new_velocity); - } - - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void on_entity_move(final EntityMoveEvent event) { - final var entity = event.getEntity(); - final var entity_id = entity.getUniqueId(); - final var block = event.getTo().getBlock(); - - if (!entities_portalling.containsKey(entity_id)) { - // Check if we walked into a portal - if (!get_module().portal_area_materials.contains(block.getType())) { - return; - } - - final var portal = get_module().portal_for(block); - if (portal == null) { - return; - } - - final var target = get_module().connected_portal(portal); - if (target == null) { - return; - } - - teleport_entity(entity, portal, target); - } else { - final var loc = entities_portalling.get(entity_id); - if (loc == null) { - // Initial teleport. Remember the current location, so we can check - // that the entity moved away far enough to allow another teleport - entities_portalling.put(entity_id, event.getFrom().clone()); - } else if (!get_module().portal_area_materials.contains(block.getType())) { - // At least 2 blocks away and outside of portal area → finish portalling. - if (loc.getWorld() == event.getFrom().getWorld() && event.getFrom().distance(loc) > 2.0) { - entities_portalling.remove(entity_id); - } - } - } - } + private final HashMap entities_portalling = new HashMap<>(); + + public PortalTeleporter(Context context) { + super(context); + } + + private boolean cancel_portal_event(final Entity entity) { + if (entities_portalling.containsKey(entity.getUniqueId())) { + return true; + } + + return get_module().is_portal_block(entity.getLocation().getBlock()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_player_portal(final PlayerPortalEvent event) { + if (cancel_portal_event(event.getPlayer())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_entity_teleport_event(final EntityTeleportEvent event) { + if (cancel_portal_event(event.getEntity())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on_entity_teleport_end_gateway_event(final EntityTeleportEndGatewayEvent event) { + // End gateway teleport can be initiated when the bounding boxes overlap, so + // the entity location will not necessarily be at the position where the end gateway block + // is. + // Therefore, we additionally check whether the initiating end gateway block is part of a + // portal structure. + // Otherwise, this event would already be handled by EntityTeleportEvent. + if (get_module().is_portal_block(event.getGateway().getBlock())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_player_teleport_event(final PlayerTeleportEndGatewayEvent event) { + final var block = event.getGateway().getBlock(); + if (get_module().is_portal_block(block)) { + event.setCancelled(true); + } + } + + private void teleport_single_entity( + final Entity entity, + final Location target_location, + final Vector new_velocity + ) { + final var entity_location = entity.getLocation(); + final var nms_entity = Nms.entity_handle(entity); + + // Now teleport. There are many ways to do it, but some are preferred over others. + // Primarily, entity.teleport() will dismount any passengers. Meh. + if (target_location.getWorld() == entity_location.getWorld()) { + if (entity instanceof Player player) { + // For players traveling in the same world, we can use the NMS player's connection's + // teleport method, which only modifies player position without dismounting. + Nms.get_player(player).connection.teleport( + target_location.getX(), + target_location.getY(), + target_location.getZ(), + target_location.getYaw(), + target_location.getPitch() + ); + } else { + // Similarly, we can just move entities. + nms_entity.absMoveTo( + target_location.getX(), + target_location.getY(), + target_location.getZ(), + target_location.getYaw(), + target_location.getPitch() + ); + } + + // For some unknown reason (SPIGOT-619) we always need to set the yaw again. + nms_entity.setYHeadRot(target_location.getYaw()); + } else { + final var passengers = new ArrayList(entity.getPassengers()); + + // Entities traveling to a different dimension need to be despawned and respawned as + // both worlds are distinct levels. + // This means they must be dismounted (or unridden) before teleportation. + passengers.stream().forEach(entity::removePassenger); + entity.teleport(target_location); + + for (var p : passengers) { + teleport_single_entity(p, target_location, new Vector()); + entity.addPassenger(p); + } + } + + // Retain velocity. Previously we needed to force-set it in the next tick, + // as apparently the movement event overrides might override the velocity. + // Now we are using our own movement events which are run outside any + // entity ticking, so no such workaround is necessary. + // schedule_next_tick(() -> { + // entity.setVelocity(new_velocity); + // }); + entity.setVelocity(new_velocity); + } + + private void teleport_entity(final Entity entity, final Portal source, Portal target) { + var target_location = target.spawn().clone(); + if (entity instanceof LivingEntity living_entity) { + // Entities in vehicles are teleported when the vehicle is teleported. + if (living_entity.isInsideVehicle()) { + return; + } + + // Increase Y value if an entity is currently flying through a portal that + // has extent in the y direction (i.e., is built upright) + if (living_entity.isGliding() && source.orientation().plane().y()) { + target_location.setY(target_location.getY() + 1.5); + } + } + + // Put null to signal initiated teleportation + final var entity_id = entity.getUniqueId(); + entities_portalling.put(entity_id, null); + + // First copy pitch & yaw to target, will be transformed soon. + final var entity_location = entity.getLocation(); + target_location.setPitch(entity_location.getPitch()); + target_location.setYaw(entity_location.getYaw()); + + // If the exit orientation of the target portal is locked, we make sure that + // the orientation of the entered portal is flipped if an entity (player) enters from the + // back. + // We have to flip the source portal orientation if the vector to be transformed + // is NOT opposing the source portal vector (i.e., not pointing against the front). + // Calculate new location (pitch, yaw) and velocity. + final var source_orientation = source.orientation(); + target_location = source_orientation.apply( + target.orientation(), + target_location, + target.exit_orientation_locked() + ); + final var new_velocity = source_orientation.apply( + target.orientation(), + entity.getVelocity(), + target.exit_orientation_locked() + ); + + teleport_single_entity(entity, target_location, new_velocity); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void on_entity_move(final EntityMoveEvent event) { + final var entity = event.getEntity(); + final var entity_id = entity.getUniqueId(); + final var block = event.getTo().getBlock(); + + if (!entities_portalling.containsKey(entity_id)) { + // Check if we walked into a portal + if (!get_module().portal_area_materials.contains(block.getType())) { + return; + } + + final var portal = get_module().portal_for(block); + if (portal == null) { + return; + } + + final var target = get_module().connected_portal(portal); + if (target == null) { + return; + } + + teleport_entity(entity, portal, target); + } else { + final var loc = entities_portalling.get(entity_id); + if (loc == null) { + // Initial teleport. Remember the current location, so we can check + // that the entity moved away far enough to allow another teleport + entities_portalling.put(entity_id, event.getFrom().clone()); + } else if (!get_module().portal_area_materials.contains(block.getType())) { + // At least 2 blocks away and outside of portal area → finish portalling. + if (loc.getWorld() == event.getFrom().getWorld() && event.getFrom().distance(loc) > 2.0) { + entities_portalling.remove(entity_id); + } + } + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/Portals.java b/vane-portals/src/main/java/org/oddlama/vane/portals/Portals.java index f23e2a0f6..100af9fdf 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/Portals.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/Portals.java @@ -1,12 +1,13 @@ package org.oddlama.vane.portals; import static org.oddlama.vane.util.BlockUtil.adjacent_blocks_3d; +import static org.oddlama.vane.util.Conversions.ms_to_ticks; import static org.oddlama.vane.util.ItemUtil.name_item; import static org.oddlama.vane.util.Nms.item_handle; import static org.oddlama.vane.util.Nms.register_entity; import static org.oddlama.vane.util.Nms.spawn; -import static org.oddlama.vane.util.Conversions.ms_to_ticks; +import com.google.common.collect.Sets; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -18,9 +19,9 @@ import java.util.UUID; import java.util.logging.Level; import java.util.stream.Collectors; - -import com.google.common.collect.Sets; - +import net.kyori.adventure.text.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; import org.apache.commons.lang.StringUtils; import org.bukkit.Chunk; import org.bukkit.Material; @@ -71,971 +72,1046 @@ import org.oddlama.vane.portals.portal.PortalBlock; import org.oddlama.vane.portals.portal.PortalBlockLookup; import org.oddlama.vane.portals.portal.Style; - -import net.kyori.adventure.text.Component; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.MobCategory; import org.oddlama.vane.util.StorageUtil; @VaneModule(name = "portals", bstats = 8642, config_version = 3, lang_version = 6, storage_version = 2) public class Portals extends Module { - // Add (de-)serializers - static { - PersistentSerializer.serializers.put(Orientation.class, x -> ((Orientation) x).name()); - PersistentSerializer.deserializers.put(Orientation.class, x -> Orientation.valueOf((String) x)); - PersistentSerializer.serializers.put(Portal.class, Portal::serialize); - PersistentSerializer.deserializers.put(Portal.class, Portal::deserialize); - PersistentSerializer.serializers.put(Portal.Visibility.class, x -> ((Portal.Visibility) x).name()); - PersistentSerializer.deserializers.put(Portal.Visibility.class, x -> Portal.Visibility.valueOf((String) x)); - PersistentSerializer.serializers.put(PortalBlock.class, PortalBlock::serialize); - PersistentSerializer.deserializers.put(PortalBlock.class, PortalBlock::deserialize); - PersistentSerializer.serializers.put(PortalBlock.Type.class, x -> ((PortalBlock.Type) x).name()); - PersistentSerializer.deserializers.put(PortalBlock.Type.class, x -> PortalBlock.Type.valueOf((String) x)); - PersistentSerializer.serializers.put(PortalBlockLookup.class, PortalBlockLookup::serialize); - PersistentSerializer.deserializers.put(PortalBlockLookup.class, PortalBlockLookup::deserialize); - PersistentSerializer.serializers.put(Style.class, Style::serialize); - PersistentSerializer.deserializers.put(Style.class, Style::deserialize); - } - - @ConfigMaterialSet(def = { Material.PISTON, Material.STICKY_PISTON }, desc = "Materials which may not be used to decorate portals.") - public Set config_blacklisted_materials; - - // TODO better and more default styles - @ConfigMaterialMapMapMap(def = { - @ConfigMaterialMapMapMapEntry(key = "vane_portals:portal_style_default", value = { - @ConfigMaterialMapMapEntry(key = "active", value = { - @ConfigMaterialMapEntry(key = "boundary_1", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_2", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_3", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_4", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_5", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), - @ConfigMaterialMapEntry(key = "origin", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "portal", value = Material.END_GATEWAY), - }), - @ConfigMaterialMapMapEntry(key = "inactive", value = { - @ConfigMaterialMapEntry(key = "boundary_1", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_2", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_3", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_4", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "boundary_5", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), - @ConfigMaterialMapEntry(key = "origin", value = Material.OBSIDIAN), - @ConfigMaterialMapEntry(key = "portal", value = Material.AIR), - }), - }), - @ConfigMaterialMapMapMapEntry(key = "vane_portals:portal_style_aqua", value = { - @ConfigMaterialMapMapEntry(key = "active", value = { - @ConfigMaterialMapEntry(key = "boundary_1", value = Material.DARK_PRISMARINE), - @ConfigMaterialMapEntry(key = "boundary_2", value = Material.WARPED_PLANKS), - @ConfigMaterialMapEntry(key = "boundary_3", value = Material.SEA_LANTERN), - @ConfigMaterialMapEntry(key = "boundary_4", value = Material.WARPED_WART_BLOCK), - @ConfigMaterialMapEntry(key = "boundary_5", value = Material.LIGHT_BLUE_STAINED_GLASS), - @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), - @ConfigMaterialMapEntry(key = "origin", value = Material.DARK_PRISMARINE), - @ConfigMaterialMapEntry(key = "portal", value = Material.END_GATEWAY), - }), - @ConfigMaterialMapMapEntry(key = "inactive", value = { - @ConfigMaterialMapEntry(key = "boundary_1", value = Material.DARK_PRISMARINE), - @ConfigMaterialMapEntry(key = "boundary_2", value = Material.WARPED_PLANKS), - @ConfigMaterialMapEntry(key = "boundary_3", value = Material.PRISMARINE_BRICKS), - @ConfigMaterialMapEntry(key = "boundary_4", value = Material.WARPED_WART_BLOCK), - @ConfigMaterialMapEntry(key = "boundary_5", value = Material.LIGHT_BLUE_STAINED_GLASS), - @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), - @ConfigMaterialMapEntry(key = "origin", value = Material.DARK_PRISMARINE), - @ConfigMaterialMapEntry(key = "portal", value = Material.AIR), - }), - }), - }, desc = "Portal style definitions. Must provide a material for each portal block type and activation state. The default style may be overridden.") - public Map>> config_styles; - - @ConfigLong(def = 10000, min = 1000, max = 110000, desc = "Delay in milliseconds after which two connected portals will automatically be disabled. Purple end-gateway beams do not show up until the maximum value of 110 seconds.") - public long config_deactivation_delay; - - @ConfigExtendedMaterial(def = "vane:decoration_end_portal_orb", desc = "The default portal icon. Also accepts heads from the head library.") - public ExtendedMaterial config_default_icon; - - @ConfigDouble(def = 0.9, min = 0.0, max = 1.0, desc = "Volume for the portal activation sound effect. 0 to disable.") - public double config_volume_activation; - - @ConfigDouble(def = 1.0, min = 0.0, max = 1.0, desc = "Volume for the portal deactivation sound effect. 0 to disable.") - public double config_volume_deactivation; - - @LangMessage - public TranslatedMessage lang_console_display_active; - - @LangMessage - public TranslatedMessage lang_console_display_inactive; - - @LangMessage - public TranslatedMessage lang_console_no_target; - - @LangMessage - public TranslatedMessage lang_unlink_restricted; - - @LangMessage - public TranslatedMessage lang_destroy_restricted; - - @LangMessage - public TranslatedMessage lang_settings_restricted; - - @LangMessage - public TranslatedMessage lang_select_target_restricted; - - // This permission allows players (usually admins) to always modify settings - // on any portal, regardless of whether other restrictions would block access. - public final Permission admin_permission; - - // Primary storage for all portals (portal_id → portal) - @Persistent - private Map storage_portals = new HashMap<>(); - private Map portals = new HashMap<>(); - - // Index for all portal blocks (world_id → chunk key → block key → portal block) - private Map>> portal_blocks_in_chunk_in_world = new HashMap<>(); - - // All loaded styles - public Map styles = new HashMap<>(); - // Cache possible area materials. This is fine as only predefined styles can - // change this. - public Set portal_area_materials = new HashSet<>(); - - // Track console items - private final Map console_floating_items = new HashMap<>(); - // Connected portals (always stores both directions!) - private final Map connected_portals = new HashMap<>(); - // Unloading ticket counter per chunk - private final Map chunk_ticket_count = new HashMap<>(); - // Disable tasks for portals - private final Map disable_tasks = new HashMap<>(); - - public PortalMenuGroup menus; - public PortalConstructor constructor; - public PortalDynmapLayer dynmap_layer; - public PortalBlueMapLayer blue_map_layer; - - public Portals() { - register_entities(); - - menus = new PortalMenuGroup(this); - new PortalActivator(this); - new PortalBlockProtector(this); - constructor = new PortalConstructor(this); - new PortalTeleporter(this); - new EntityMoveProcessor(this); - dynmap_layer = new PortalDynmapLayer(this); - blue_map_layer = new PortalBlueMapLayer(this); - - // Register admin permission - admin_permission = new Permission( - "vane." + get_module().get_name() + ".admin", - "Allows administration of any portal", - PermissionDefault.OP); - get_module().register_permission(admin_permission); - - // TODO legacy, remove in v2. - persistent_storage_manager.add_migration_to( - 2, - "Portal visibility GROUP_INTERNAL was added. This is a no-op.", - json -> { - }); - } - - @SuppressWarnings("unchecked") - private void register_entities() { - get_module().core.unfreeze_registries(); - register_entity( - NamespacedKey.minecraft("item"), - namespace(), - "floating_item", - EntityType.Builder.of(FloatingItem::new, MobCategory.MISC).sized(0.0f, 0.0f)); - } - - private static long block_key(final Block block) { - return (block.getY() << 8) - | ((block.getX() & 0xF) << 4) - | ((block.getZ() & 0xF)); - } - - private static Block unpack_block_key(final Chunk chunk, long block_key) { - int y = (int) (block_key >> 8); - int x = (int) ((block_key >> 4) & 0xF); - int z = (int) (block_key & 0xF); - return chunk.getBlock(x, y, z); - } - - @Override - public void on_config_change() { - styles.clear(); - - config_styles.forEach((style_key, v1) -> { - final var split = style_key.split(":"); - if (split.length != 2) { - throw new RuntimeException("Invalid style key: '" + style_key + "' is not a valid namespaced key"); - } - - final var style = new Style(StorageUtil.namespaced_key(split[0], split[1])); - v1.forEach((is_active, v2) -> { - final boolean active; - switch (is_active) { - case "active": - active = true; - break; - case "inactive": - active = false; - break; - default: - throw new RuntimeException("Invalid active state, must be either 'active' or 'inactive'"); - } - - v2.forEach((portal_block_type, material) -> { - final var type = PortalBlock.Type.valueOf(portal_block_type.toUpperCase()); - style.set_material(active, type, material); - }); - }); - - // Check validity and add to map. - style.check_valid(); - styles.put(style.key(), style); - }); - - if (!styles.containsKey(Style.default_style_key())) { - // Add default style if it wasn't overridden - final var default_style = Style.default_style(); - styles.put(default_style.key(), default_style); - } - - portal_area_materials.clear(); - // Acquire material set from styles. Will be used to speed up event checking. - for (final var style : styles.values()) { - portal_area_materials.add(style.material(true, PortalBlock.Type.PORTAL)); - } - } - - // Lightweight callbacks to the regions module, if it is installed. - // Lifting the callback storage into the portals module saves us - // from having to ship regions api with this module. - private Function2 is_in_same_region_group_callback = null; - - public void set_is_in_same_region_group_callback(final Function2 callback) { - is_in_same_region_group_callback = callback; - } - - private Function2 player_can_use_portals_in_region_group_of_callback = null; - - public void set_player_can_use_portals_in_region_group_of_callback( - final Function2 callback) { - player_can_use_portals_in_region_group_of_callback = callback; - } - - public boolean is_in_same_region_group(final Portal a, final Portal b) { - if (is_in_same_region_group_callback == null) { - return true; - } - return is_in_same_region_group_callback.apply(a, b); - } - - public boolean player_can_use_portals_in_region_group_of(final Player player, final Portal portal) { - if (player_can_use_portals_in_region_group_of_callback == null) { - return true; - } - return player_can_use_portals_in_region_group_of_callback.apply(player, portal); - } - - public boolean is_regions_installed() { - return is_in_same_region_group_callback != null; - } - - public Style style(final NamespacedKey key) { - final var s = styles.get(key); - if (s == null) { - log.warning("Encountered invalid style " + key + ", falling back to default style."); - return styles.get(Style.default_style_key()); - } else { - return s; - } - } - - public void remove_portal(final Portal portal) { - // Deactivate portal if needed - final var connected = connected_portal(portal); - if (connected != null) { - disconnect_portals(portal, connected); - } - - // Remove portal from storage - if (portals.remove(portal.id()) == null) { - // Was already removed - return; - } - - // Remove portal blocks - portal.blocks().forEach(this::remove_portal_block); - - // Replace references to the portal everywhere - // and update all changed portal consoles. - for (final var other : portals.values()) { - if (Objects.equals(other.target_id(), portal.id())) { - other.target_id(null); - other - .blocks() - .stream() - .filter(pb -> pb.type() == PortalBlock.Type.CONSOLE) - .filter(pb -> console_floating_items.containsKey(pb.block())) - .forEach(pb -> update_console_item(other, pb.block())); - } - } - - // Force update storage now, as a precaution. - update_persistent_data(); - - // Close and taint all related open menus - get_module().core.menu_manager.for_each_open((player, menu) -> { - if (menu.tag() instanceof PortalMenuTag && - Objects.equals(((PortalMenuTag) menu.tag()).portal_id(), portal.id())) { - menu.taint(); - menu.close(player); - } - }); - - // Remove map marker - remove_marker(portal.id()); - - // Play sound - portal - .spawn() - .getWorld() - .playSound(portal.spawn(), Sound.ENTITY_ENDER_EYE_DEATH, SoundCategory.BLOCKS, 1.0f, 1.0f); - } - - public void add_new_portal(final Portal portal) { - portal.invalidated = true; - - // Index the new portal - index_portal(portal); - - // Play sound - portal - .spawn() - .getWorld() - .playSound(portal.spawn(), Sound.ENTITY_ENDER_EYE_DEATH, SoundCategory.BLOCKS, 1.0f, 2.0f); - } - - public void index_portal(final Portal portal) { - portals.put(portal.id(), portal); - portal.blocks().forEach(b -> index_portal_block(portal, b)); - - // Create map marker - update_marker(portal); - } - - public Collection all_available_portals() { - return portals.values().stream() - .filter(p -> p.spawn().isWorldLoaded()) - .collect(Collectors.toList()); - } - - public void remove_portal_block(final PortalBlock portal_block) { - // Restore original block - switch (portal_block.type()) { - case ORIGIN: - portal_block.block().setType(constructor.config_material_origin); - break; - case CONSOLE: - portal_block.block().setType(constructor.config_material_console); - break; - case BOUNDARY_1: - portal_block.block().setType(constructor.config_material_boundary_1); - break; - case BOUNDARY_2: - portal_block.block().setType(constructor.config_material_boundary_2); - break; - case BOUNDARY_3: - portal_block.block().setType(constructor.config_material_boundary_3); - break; - case BOUNDARY_4: - portal_block.block().setType(constructor.config_material_boundary_4); - break; - case BOUNDARY_5: - portal_block.block().setType(constructor.config_material_boundary_5); - break; - case PORTAL: - portal_block.block().setType(constructor.config_material_portal_area); - break; - } - - // Remove console item if a block is a console - if (portal_block.type() == PortalBlock.Type.CONSOLE) { - remove_console_item(portal_block.block()); - } - - // Remove from acceleration structure - final var block = portal_block.block(); - final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(block.getWorld().getUID()); - if (portal_blocks_in_chunk == null) { - return; - } - - final var chunk_key = block.getChunk().getChunkKey(); - final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); - if (block_to_portal_block == null) { - return; - } - - block_to_portal_block.remove(block_key(block)); - - // Spawn effect if not portal area - if (portal_block.type() != PortalBlock.Type.PORTAL) { - portal_block.block().getWorld() - .spawnParticle(Particle.ENCHANT, portal_block.block().getLocation().add(0.5, 0.5, 0.5), 50, 0.0, - 0.0, 0.0, 1.0); - } - } - - public void remove_portal_block(final Portal portal, final PortalBlock portal_block) { - // Remove from portal - portal.blocks().remove(portal_block); - - // Remove from acceleration structure - remove_portal_block(portal_block); - } - - public void add_new_portal_block(final Portal portal, final PortalBlock portal_block) { - // Add to portal - portal.blocks().add(portal_block); - portal.invalidated = true; - - index_portal_block(portal, portal_block); - - // Spawn effect if not portal area - if (portal_block.type() != PortalBlock.Type.PORTAL) { - portal_block.block().getWorld() - .spawnParticle(Particle.PORTAL, portal_block.block().getLocation().add(0.5, 0.5, 0.5), 50, 0.0, 0.0, - 0.0, 1.0); - } - } - - public void index_portal_block(final Portal portal, final PortalBlock portal_block) { - // Add to acceleration structure - final var block = portal_block.block(); - final var world_id = block.getWorld().getUID(); - var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.computeIfAbsent(world_id, k -> new HashMap<>()); - - final var chunk_key = block.getChunk().getChunkKey(); - var block_to_portal_block = portal_blocks_in_chunk.computeIfAbsent(chunk_key, k -> new HashMap<>()); - - block_to_portal_block.put(block_key(block), portal_block.lookup(portal.id())); - } - - public PortalBlockLookup portal_block_for(final Block block) { - final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(block.getWorld().getUID()); - if (portal_blocks_in_chunk == null) { - return null; - } - - final var chunk_key = block.getChunk().getChunkKey(); - final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); - if (block_to_portal_block == null) { - return null; - } - - return block_to_portal_block.get(block_key(block)); - } - - public Portal portal_for(@Nullable final UUID uuid) { - final var portal = portals.get(uuid); - if (portal == null || !portal.spawn().isWorldLoaded()) { - return null; - } - return portal; - } - - public Portal portal_for(@NotNull final PortalBlockLookup block) { - return portal_for(block.portal_id()); - } - - public Portal portal_for(final Block block) { - final var portal_block = portal_block_for(block); - if (portal_block == null) { - return null; - } - - return portal_for(portal_block); - } - - public boolean is_portal_block(final Block block) { - final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(block.getWorld().getUID()); - if (portal_blocks_in_chunk == null) { - return false; - } - - final var chunk_key = block.getChunk().getChunkKey(); - final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); - if (block_to_portal_block == null) { - return false; - } - - return block_to_portal_block.containsKey(block_key(block)); - } - - public Portal controlled_portal(final Block block) { - final var root_portal = portal_for(block); - if (root_portal != null) { - return root_portal; - } - - // Find adjacent console blocks in full 3x3x3 cube, which will make this block a - // controlling block - for (final var adj : adjacent_blocks_3d(block)) { - final var portal_block = portal_block_for(adj); - if (portal_block != null && portal_block.type() == PortalBlock.Type.CONSOLE) { - return portal_for(portal_block); - } - } - - return null; - } - - public Set chunks_for(final Portal portal) { - if (portal == null) { - return new HashSet(); - } - - final var set = new HashSet(); - for (final var pb : portal.blocks()) { - set.add(pb.block().getChunk()); - } - return set; - } - - public void load_portal_chunks(final Portal portal) { - // Load chunks and adds a ticket, so they get loaded and are kept loaded - for (final var chunk : chunks_for(portal)) { - final var chunk_key = chunk.getChunkKey(); - final var ticket_counter = chunk_ticket_count.get(chunk_key); - if (ticket_counter == null) { - chunk.addPluginChunkTicket(this); - chunk_ticket_count.put(chunk_key, 1); - } else { - chunk_ticket_count.put(chunk_key, ticket_counter + 1); - } - } - } - - public void allow_unload_portal_chunks(final Portal portal) { - // Removes the ticket so chunks can be unloaded again - for (final var chunk : chunks_for(portal)) { - final var chunk_key = chunk.getChunkKey(); - final var ticket_counter = chunk_ticket_count.get(chunk_key); - - if (ticket_counter > 1) { - chunk_ticket_count.put(chunk_key, ticket_counter - 1); - } else if (ticket_counter == 1) { - chunk.removePluginChunkTicket(this); - chunk_ticket_count.remove(chunk_key); - } - } - } - - public void connect_portals(final Portal src, final Portal dst) { - // Load chunks - load_portal_chunks(src); - load_portal_chunks(dst); - - // Add to map - connected_portals.put(src.id(), dst.id()); - connected_portals.put(dst.id(), src.id()); - - // Activate both - src.on_connect(this, dst); - dst.on_connect(this, src); - - // Schedule automatic disable - start_disable_task(src, dst); - } - - public void disconnect_portals(final Portal src) { - disconnect_portals(src, portal_for(connected_portals.get(src.id()))); - } - - public void disconnect_portals(final Portal src, final Portal dst) { - if (src == null || dst == null) { - return; - } - - // Allow unloading chunks again - allow_unload_portal_chunks(src); - allow_unload_portal_chunks(dst); - - // Remove from a map - connected_portals.remove(src.id()); - connected_portals.remove(dst.id()); - - // Deactivate both - src.on_disconnect(this, dst); - dst.on_disconnect(this, src); - - // Reset target id's if the target portal was transient and - // the target isn't locked. - if (dst.visibility().is_transient_target() && !src.target_locked()) { - src.target_id(null); - src.update_blocks(this); - } - - // Remove an automatic disable task if existing - stop_disable_task(src, dst); - } - - private void start_disable_task(final Portal portal, final Portal target) { - stop_disable_task(portal, target); - final var task = schedule_task( - new PortalDisableRunnable(portal, target), - ms_to_ticks(config_deactivation_delay)); - disable_tasks.put(portal.id(), task); - disable_tasks.put(target.id(), task); - } - - private void stop_disable_task(final Portal portal, final Portal target) { - final var task1 = disable_tasks.remove(portal.id()); - final var task2 = disable_tasks.remove(target.id()); - if (task1 != null) { - task1.cancel(); - } - if (task2 != null && task2 != task1) { - task2.cancel(); - } - } - - @Override - public void on_disable() { - // Disable all portals now - for (final var id : new ArrayList<>(connected_portals.keySet())) { - disconnect_portals(portal_for(id)); - } - - // Remove all console items, and all chunk tickets - chunk_ticket_count.clear(); - for (final var world : getServer().getWorlds()) { - for (final var chunk : world.getLoadedChunks()) { - // Remove console item - for_each_console_block_in_chunk(chunk, (block, console) -> remove_console_item(block)); - // Allow chunk unloading - chunk.removePluginChunkTicket(this); - } - } - - // Save data - update_persistent_data(); - super.on_disable(); - } - - public boolean is_activated(final Portal portal) { - return connected_portals.containsKey(portal.id()); - } - - public Portal connected_portal(final Portal portal) { - final var connected_id = connected_portals.get(portal.id()); - if (connected_id == null) { - return null; - } - return portal_for(connected_id); - } - - public ItemStack icon_for(final Portal portal) { - final var item = portal.icon(); - if (item == null) { - return config_default_icon.item(); - } else { - return item; - } - } - - private ItemStack make_console_item(final Portal portal, boolean active) { - final Portal target; - if (active) { - target = connected_portal(portal); - } else { - target = portal.target(this); - } - - // Try to use target portal's block - ItemStack item = null; - if (target != null) { - item = target.icon(); - } - - // Fallback item - if (item == null) { - item = config_default_icon.item(); - } - - final var target_name = target == null ? lang_console_no_target.str() : target.name(); - final Component display_name; - if (active) { - display_name = lang_console_display_active.format("§5" + target_name); - } else { - display_name = lang_console_display_inactive.format("§7" + target_name); - } - - return name_item(item, display_name); - } - - public void update_portal_icon(final Portal portal) { - // Update map marker, as name could have changed - update_marker(portal); - - for (final var active_console : console_floating_items.keySet()) { - final var portal_block = portal_block_for(active_console); - final var other = portal_for(portal_block); - if (Objects.equals(other.target_id(), portal.id())) { - update_console_item(other, active_console); - } - } - } - - public void update_portal_visibility(final Portal portal) { - // Replace references to the portal everywhere if visibility - // has changed. - switch (portal.visibility()) { - case PRIVATE: - case GROUP: - // Not visible from outside, these are transient. - for (final var other : portals.values()) { - if (Objects.equals(other.target_id(), portal.id())) { - other.target_id(null); - } - } - break; - case GROUP_INTERNAL: - // Remove from portals outside the group - for (final var other : portals.values()) { - if (Objects.equals(other.target_id(), portal.id()) && !is_in_same_region_group(other, portal)) { - other.target_id(null); - } - } - break; - default: // Nothing to do - break; - } - - // Update map marker - update_marker(portal); - } - - public void update_console_item(final Portal portal, final Block block) { - var console_item = console_floating_items.get(block); - final boolean is_new; - if (console_item == null) { - console_item = new FloatingItem(block.getWorld(), block.getX() + 0.5, block.getY() + 1.2, - block.getZ() + 0.5); - is_new = true; - } else { - is_new = false; - } - - final var active = is_activated(portal); - console_item.setItem(item_handle(make_console_item(portal, active))); - - if (is_new) { - console_floating_items.put(block, console_item); - spawn(block.getWorld(), console_item); - } - } - - public void remove_console_item(final Block block) { - final var console_item = console_floating_items.remove(block); - if (console_item != null) { - console_item.discard(); - } - } - - private void for_each_console_block_in_chunk( - final Chunk chunk, - final Consumer2 consumer) { - final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(chunk.getWorld().getUID()); - if (portal_blocks_in_chunk == null) { - return; - } - - final var chunk_key = chunk.getChunkKey(); - final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); - if (block_to_portal_block == null) { - return; - } - - block_to_portal_block.forEach((k, v) -> { - if (v.type() == PortalBlock.Type.CONSOLE) { - consumer.apply(unpack_block_key(chunk, k), v); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_monitor_chunk_unload(final ChunkUnloadEvent event) { - final var chunk = event.getChunk(); - - // Disable all consoles in this chunk - for_each_console_block_in_chunk(chunk, (block, console) -> remove_console_item(block)); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on_monitor_chunk_load(final ChunkLoadEvent event) { - final var chunk = event.getChunk(); - - // Enable all consoles in this chunk - for_each_console_block_in_chunk( - chunk, - (block, console) -> { - final var portal = portal_for(console.portal_id()); - update_console_item(portal, block); - }); - } - - public void update_marker(final Portal portal) { - dynmap_layer.update_marker(portal); - blue_map_layer.update_marker(portal); - } - - public void remove_marker(final UUID portal_id) { - dynmap_layer.remove_marker(portal_id); - blue_map_layer.remove_marker(portal_id); - } - - @EventHandler - public void on_save_world(final WorldSaveEvent event) { - update_persistent_data(event.getWorld()); - } - - @EventHandler - public void on_load_world(final WorldLoadEvent event) { - load_persistent_data(event.getWorld()); - } - - @EventHandler - public void on_unload_world(final WorldUnloadEvent event) { - // Save data before unloading a world (not called on stop) - update_persistent_data(event.getWorld()); - } - - public void update_persistent_data() { - for (final var world : getServer().getWorlds()) { - update_persistent_data(world); - } - } - - public static final NamespacedKey STORAGE_PORTALS = StorageUtil.namespaced_key("vane_portals", "portals"); - - public void load_persistent_data(final World world) { - final var data = world.getPersistentDataContainer(); - final var storage_portal_prefix = STORAGE_PORTALS + "."; - - // Load all currently stored portals. - final var pdc_portals = data.getKeys().stream() - .filter(key -> key.toString().startsWith(storage_portal_prefix)) - .map(key -> StringUtils.removeStart(key.toString(), storage_portal_prefix)) - .map(uuid -> UUID.fromString(uuid)) - .collect(Collectors.toSet()); - - for (final var portal_id : pdc_portals) { - final var json_bytes = data.get(NamespacedKey.fromString(storage_portal_prefix + portal_id.toString()), - PersistentDataType.BYTE_ARRAY); - try { - final var portal = PersistentSerializer.from_json(Portal.class, new JSONObject(new String(json_bytes))); - index_portal(portal); - } catch (IOException e) { - log.log(Level.SEVERE, "error while serializing persistent data!", e); - } - } - log.log(Level.INFO, - "Loaded " + pdc_portals.size() + " portals for world " + world.getName() + "(" + world.getUID() + ")"); - - // Convert portals from legacy storage - final Set remove_from_legacy_storage = new HashSet<>(); - int converted = 0; - for (final var portal : storage_portals.values()) { - if (!portal.spawn_world().equals(world.getUID())) { - continue; - } - - if (portals.containsKey(portal.id())) { - remove_from_legacy_storage.add(portal.id()); - continue; - } - - index_portal(portal); - portal.invalidated = true; - converted += 1; - } - - // Remove any portal that was successfully loaded from the new storage. - remove_from_legacy_storage.forEach(storage_portals::remove); - if (remove_from_legacy_storage.size() > 0) { - mark_persistent_storage_dirty(); - } - - // Update all consoles in the loaded world. These - // might be missed by chunk load event as it runs asynchronous - // to this function, and it can't be synchronized without annoying the server. - for (final var chunk : world.getLoadedChunks()) { - for_each_console_block_in_chunk( - chunk, - (block, console) -> { - final var portal = portal_for(console.portal_id()); - update_console_item(portal, block); - }); - } - - // Save if we had any conversions - if (converted > 0) { - update_persistent_data(); - } - } - - public void update_persistent_data(final World world) { - final var data = world.getPersistentDataContainer(); - final var storage_portal_prefix = STORAGE_PORTALS + "."; - - // Update invalidated portals - portals.values().stream() - .filter(x -> x.invalidated && x.spawn_world().equals(world.getUID())) - .forEach(portal -> { - try { - final var json = PersistentSerializer.to_json(Portal.class, portal); - data.set(NamespacedKey.fromString(storage_portal_prefix + portal.id().toString()), - PersistentDataType.BYTE_ARRAY, json.toString().getBytes()); - } catch (IOException e) { - log.log(Level.SEVERE, "error while serializing persistent data!", e); - return; - } - - portal.invalidated = false; - }); - - // Get all currently stored portals. - final var stored_portals = data.getKeys().stream() - .filter(key -> key.toString().startsWith(storage_portal_prefix)) - .map(key -> StringUtils.removeStart(key.toString(), storage_portal_prefix)) - .map(uuid -> UUID.fromString(uuid)) - .collect(Collectors.toSet()); - - // Remove all portals that no longer exist - Sets.difference(stored_portals, portals.keySet()) - .forEach(id -> data.remove(NamespacedKey.fromString(storage_portal_prefix + id.toString()))); - } - - private class PortalDisableRunnable implements Runnable { - - private Portal src; - private Portal dst; - - public PortalDisableRunnable(final Portal src, final Portal dst) { - this.src = src; - this.dst = dst; - } - - @Override - public void run() { - Portals.this.disconnect_portals(src, dst); - } - } + // Add (de-)serializers + static { + PersistentSerializer.serializers.put(Orientation.class, x -> ((Orientation) x).name()); + PersistentSerializer.deserializers.put(Orientation.class, x -> Orientation.valueOf((String) x)); + PersistentSerializer.serializers.put(Portal.class, Portal::serialize); + PersistentSerializer.deserializers.put(Portal.class, Portal::deserialize); + PersistentSerializer.serializers.put(Portal.Visibility.class, x -> ((Portal.Visibility) x).name()); + PersistentSerializer.deserializers.put(Portal.Visibility.class, x -> Portal.Visibility.valueOf((String) x)); + PersistentSerializer.serializers.put(PortalBlock.class, PortalBlock::serialize); + PersistentSerializer.deserializers.put(PortalBlock.class, PortalBlock::deserialize); + PersistentSerializer.serializers.put(PortalBlock.Type.class, x -> ((PortalBlock.Type) x).name()); + PersistentSerializer.deserializers.put(PortalBlock.Type.class, x -> PortalBlock.Type.valueOf((String) x)); + PersistentSerializer.serializers.put(PortalBlockLookup.class, PortalBlockLookup::serialize); + PersistentSerializer.deserializers.put(PortalBlockLookup.class, PortalBlockLookup::deserialize); + PersistentSerializer.serializers.put(Style.class, Style::serialize); + PersistentSerializer.deserializers.put(Style.class, Style::deserialize); + } + + @ConfigMaterialSet( + def = { Material.PISTON, Material.STICKY_PISTON }, + desc = "Materials which may not be used to decorate portals." + ) + public Set config_blacklisted_materials; + + // TODO better and more default styles + @ConfigMaterialMapMapMap( + def = { + @ConfigMaterialMapMapMapEntry( + key = "vane_portals:portal_style_default", + value = { + @ConfigMaterialMapMapEntry( + key = "active", + value = { + @ConfigMaterialMapEntry(key = "boundary_1", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_2", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_3", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_4", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_5", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), + @ConfigMaterialMapEntry(key = "origin", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "portal", value = Material.END_GATEWAY), + } + ), + @ConfigMaterialMapMapEntry( + key = "inactive", + value = { + @ConfigMaterialMapEntry(key = "boundary_1", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_2", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_3", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_4", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "boundary_5", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), + @ConfigMaterialMapEntry(key = "origin", value = Material.OBSIDIAN), + @ConfigMaterialMapEntry(key = "portal", value = Material.AIR), + } + ), + } + ), + @ConfigMaterialMapMapMapEntry( + key = "vane_portals:portal_style_aqua", + value = { + @ConfigMaterialMapMapEntry( + key = "active", + value = { + @ConfigMaterialMapEntry(key = "boundary_1", value = Material.DARK_PRISMARINE), + @ConfigMaterialMapEntry(key = "boundary_2", value = Material.WARPED_PLANKS), + @ConfigMaterialMapEntry(key = "boundary_3", value = Material.SEA_LANTERN), + @ConfigMaterialMapEntry(key = "boundary_4", value = Material.WARPED_WART_BLOCK), + @ConfigMaterialMapEntry(key = "boundary_5", value = Material.LIGHT_BLUE_STAINED_GLASS), + @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), + @ConfigMaterialMapEntry(key = "origin", value = Material.DARK_PRISMARINE), + @ConfigMaterialMapEntry(key = "portal", value = Material.END_GATEWAY), + } + ), + @ConfigMaterialMapMapEntry( + key = "inactive", + value = { + @ConfigMaterialMapEntry(key = "boundary_1", value = Material.DARK_PRISMARINE), + @ConfigMaterialMapEntry(key = "boundary_2", value = Material.WARPED_PLANKS), + @ConfigMaterialMapEntry(key = "boundary_3", value = Material.PRISMARINE_BRICKS), + @ConfigMaterialMapEntry(key = "boundary_4", value = Material.WARPED_WART_BLOCK), + @ConfigMaterialMapEntry(key = "boundary_5", value = Material.LIGHT_BLUE_STAINED_GLASS), + @ConfigMaterialMapEntry(key = "console", value = Material.ENCHANTING_TABLE), + @ConfigMaterialMapEntry(key = "origin", value = Material.DARK_PRISMARINE), + @ConfigMaterialMapEntry(key = "portal", value = Material.AIR), + } + ), + } + ), + }, + desc = "Portal style definitions. Must provide a material for each portal block type and activation state. The default style may be overridden." + ) + public Map>> config_styles; + + @ConfigLong( + def = 10000, + min = 1000, + max = 110000, + desc = "Delay in milliseconds after which two connected portals will automatically be disabled. Purple end-gateway beams do not show up until the maximum value of 110 seconds." + ) + public long config_deactivation_delay; + + @ConfigExtendedMaterial( + def = "vane:decoration_end_portal_orb", + desc = "The default portal icon. Also accepts heads from the head library." + ) + public ExtendedMaterial config_default_icon; + + @ConfigDouble( + def = 0.9, + min = 0.0, + max = 1.0, + desc = "Volume for the portal activation sound effect. 0 to disable." + ) + public double config_volume_activation; + + @ConfigDouble( + def = 1.0, + min = 0.0, + max = 1.0, + desc = "Volume for the portal deactivation sound effect. 0 to disable." + ) + public double config_volume_deactivation; + + @LangMessage + public TranslatedMessage lang_console_display_active; + + @LangMessage + public TranslatedMessage lang_console_display_inactive; + + @LangMessage + public TranslatedMessage lang_console_no_target; + + @LangMessage + public TranslatedMessage lang_unlink_restricted; + + @LangMessage + public TranslatedMessage lang_destroy_restricted; + + @LangMessage + public TranslatedMessage lang_settings_restricted; + + @LangMessage + public TranslatedMessage lang_select_target_restricted; + + // This permission allows players (usually admins) to always modify settings + // on any portal, regardless of whether other restrictions would block access. + public final Permission admin_permission; + + // Primary storage for all portals (portal_id → portal) + @Persistent + private Map storage_portals = new HashMap<>(); + + private Map portals = new HashMap<>(); + + // Index for all portal blocks (world_id → chunk key → block key → portal block) + private Map>> portal_blocks_in_chunk_in_world = new HashMap<>(); + + // All loaded styles + public Map styles = new HashMap<>(); + // Cache possible area materials. This is fine as only predefined styles can + // change this. + public Set portal_area_materials = new HashSet<>(); + + // Track console items + private final Map console_floating_items = new HashMap<>(); + // Connected portals (always stores both directions!) + private final Map connected_portals = new HashMap<>(); + // Unloading ticket counter per chunk + private final Map chunk_ticket_count = new HashMap<>(); + // Disable tasks for portals + private final Map disable_tasks = new HashMap<>(); + + public PortalMenuGroup menus; + public PortalConstructor constructor; + public PortalDynmapLayer dynmap_layer; + public PortalBlueMapLayer blue_map_layer; + + public Portals() { + register_entities(); + + menus = new PortalMenuGroup(this); + new PortalActivator(this); + new PortalBlockProtector(this); + constructor = new PortalConstructor(this); + new PortalTeleporter(this); + new EntityMoveProcessor(this); + dynmap_layer = new PortalDynmapLayer(this); + blue_map_layer = new PortalBlueMapLayer(this); + + // Register admin permission + admin_permission = new Permission( + "vane." + get_module().get_name() + ".admin", + "Allows administration of any portal", + PermissionDefault.OP + ); + get_module().register_permission(admin_permission); + + // TODO legacy, remove in v2. + persistent_storage_manager.add_migration_to( + 2, + "Portal visibility GROUP_INTERNAL was added. This is a no-op.", + json -> {} + ); + } + + @SuppressWarnings("unchecked") + private void register_entities() { + get_module().core.unfreeze_registries(); + register_entity( + NamespacedKey.minecraft("item"), + namespace(), + "floating_item", + EntityType.Builder.of(FloatingItem::new, MobCategory.MISC).sized(0.0f, 0.0f) + ); + } + + private static long block_key(final Block block) { + return (block.getY() << 8) | ((block.getX() & 0xF) << 4) | ((block.getZ() & 0xF)); + } + + private static Block unpack_block_key(final Chunk chunk, long block_key) { + int y = (int) (block_key >> 8); + int x = (int) ((block_key >> 4) & 0xF); + int z = (int) (block_key & 0xF); + return chunk.getBlock(x, y, z); + } + + @Override + public void on_config_change() { + styles.clear(); + + config_styles.forEach((style_key, v1) -> { + final var split = style_key.split(":"); + if (split.length != 2) { + throw new RuntimeException("Invalid style key: '" + style_key + "' is not a valid namespaced key"); + } + + final var style = new Style(StorageUtil.namespaced_key(split[0], split[1])); + v1.forEach((is_active, v2) -> { + final boolean active; + switch (is_active) { + case "active": + active = true; + break; + case "inactive": + active = false; + break; + default: + throw new RuntimeException("Invalid active state, must be either 'active' or 'inactive'"); + } + + v2.forEach((portal_block_type, material) -> { + final var type = PortalBlock.Type.valueOf(portal_block_type.toUpperCase()); + style.set_material(active, type, material); + }); + }); + + // Check validity and add to map. + style.check_valid(); + styles.put(style.key(), style); + }); + + if (!styles.containsKey(Style.default_style_key())) { + // Add default style if it wasn't overridden + final var default_style = Style.default_style(); + styles.put(default_style.key(), default_style); + } + + portal_area_materials.clear(); + // Acquire material set from styles. Will be used to speed up event checking. + for (final var style : styles.values()) { + portal_area_materials.add(style.material(true, PortalBlock.Type.PORTAL)); + } + } + + // Lightweight callbacks to the regions module, if it is installed. + // Lifting the callback storage into the portals module saves us + // from having to ship regions api with this module. + private Function2 is_in_same_region_group_callback = null; + + public void set_is_in_same_region_group_callback(final Function2 callback) { + is_in_same_region_group_callback = callback; + } + + private Function2 player_can_use_portals_in_region_group_of_callback = null; + + public void set_player_can_use_portals_in_region_group_of_callback( + final Function2 callback + ) { + player_can_use_portals_in_region_group_of_callback = callback; + } + + public boolean is_in_same_region_group(final Portal a, final Portal b) { + if (is_in_same_region_group_callback == null) { + return true; + } + return is_in_same_region_group_callback.apply(a, b); + } + + public boolean player_can_use_portals_in_region_group_of(final Player player, final Portal portal) { + if (player_can_use_portals_in_region_group_of_callback == null) { + return true; + } + return player_can_use_portals_in_region_group_of_callback.apply(player, portal); + } + + public boolean is_regions_installed() { + return is_in_same_region_group_callback != null; + } + + public Style style(final NamespacedKey key) { + final var s = styles.get(key); + if (s == null) { + log.warning("Encountered invalid style " + key + ", falling back to default style."); + return styles.get(Style.default_style_key()); + } else { + return s; + } + } + + public void remove_portal(final Portal portal) { + // Deactivate portal if needed + final var connected = connected_portal(portal); + if (connected != null) { + disconnect_portals(portal, connected); + } + + // Remove portal from storage + if (portals.remove(portal.id()) == null) { + // Was already removed + return; + } + + // Remove portal blocks + portal.blocks().forEach(this::remove_portal_block); + + // Replace references to the portal everywhere + // and update all changed portal consoles. + for (final var other : portals.values()) { + if (Objects.equals(other.target_id(), portal.id())) { + other.target_id(null); + other + .blocks() + .stream() + .filter(pb -> pb.type() == PortalBlock.Type.CONSOLE) + .filter(pb -> console_floating_items.containsKey(pb.block())) + .forEach(pb -> update_console_item(other, pb.block())); + } + } + + // Force update storage now, as a precaution. + update_persistent_data(); + + // Close and taint all related open menus + get_module() + .core.menu_manager.for_each_open((player, menu) -> { + if ( + menu.tag() instanceof PortalMenuTag && + Objects.equals(((PortalMenuTag) menu.tag()).portal_id(), portal.id()) + ) { + menu.taint(); + menu.close(player); + } + }); + + // Remove map marker + remove_marker(portal.id()); + + // Play sound + portal + .spawn() + .getWorld() + .playSound(portal.spawn(), Sound.ENTITY_ENDER_EYE_DEATH, SoundCategory.BLOCKS, 1.0f, 1.0f); + } + + public void add_new_portal(final Portal portal) { + portal.invalidated = true; + + // Index the new portal + index_portal(portal); + + // Play sound + portal + .spawn() + .getWorld() + .playSound(portal.spawn(), Sound.ENTITY_ENDER_EYE_DEATH, SoundCategory.BLOCKS, 1.0f, 2.0f); + } + + public void index_portal(final Portal portal) { + portals.put(portal.id(), portal); + portal.blocks().forEach(b -> index_portal_block(portal, b)); + + // Create map marker + update_marker(portal); + } + + public Collection all_available_portals() { + return portals.values().stream().filter(p -> p.spawn().isWorldLoaded()).collect(Collectors.toList()); + } + + public void remove_portal_block(final PortalBlock portal_block) { + // Restore original block + switch (portal_block.type()) { + case ORIGIN: + portal_block.block().setType(constructor.config_material_origin); + break; + case CONSOLE: + portal_block.block().setType(constructor.config_material_console); + break; + case BOUNDARY_1: + portal_block.block().setType(constructor.config_material_boundary_1); + break; + case BOUNDARY_2: + portal_block.block().setType(constructor.config_material_boundary_2); + break; + case BOUNDARY_3: + portal_block.block().setType(constructor.config_material_boundary_3); + break; + case BOUNDARY_4: + portal_block.block().setType(constructor.config_material_boundary_4); + break; + case BOUNDARY_5: + portal_block.block().setType(constructor.config_material_boundary_5); + break; + case PORTAL: + portal_block.block().setType(constructor.config_material_portal_area); + break; + } + + // Remove console item if a block is a console + if (portal_block.type() == PortalBlock.Type.CONSOLE) { + remove_console_item(portal_block.block()); + } + + // Remove from acceleration structure + final var block = portal_block.block(); + final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(block.getWorld().getUID()); + if (portal_blocks_in_chunk == null) { + return; + } + + final var chunk_key = block.getChunk().getChunkKey(); + final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); + if (block_to_portal_block == null) { + return; + } + + block_to_portal_block.remove(block_key(block)); + + // Spawn effect if not portal area + if (portal_block.type() != PortalBlock.Type.PORTAL) { + portal_block + .block() + .getWorld() + .spawnParticle( + Particle.ENCHANT, + portal_block.block().getLocation().add(0.5, 0.5, 0.5), + 50, + 0.0, + 0.0, + 0.0, + 1.0 + ); + } + } + + public void remove_portal_block(final Portal portal, final PortalBlock portal_block) { + // Remove from portal + portal.blocks().remove(portal_block); + + // Remove from acceleration structure + remove_portal_block(portal_block); + } + + public void add_new_portal_block(final Portal portal, final PortalBlock portal_block) { + // Add to portal + portal.blocks().add(portal_block); + portal.invalidated = true; + + index_portal_block(portal, portal_block); + + // Spawn effect if not portal area + if (portal_block.type() != PortalBlock.Type.PORTAL) { + portal_block + .block() + .getWorld() + .spawnParticle( + Particle.PORTAL, + portal_block.block().getLocation().add(0.5, 0.5, 0.5), + 50, + 0.0, + 0.0, + 0.0, + 1.0 + ); + } + } + + public void index_portal_block(final Portal portal, final PortalBlock portal_block) { + // Add to acceleration structure + final var block = portal_block.block(); + final var world_id = block.getWorld().getUID(); + var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.computeIfAbsent(world_id, k -> new HashMap<>()); + + final var chunk_key = block.getChunk().getChunkKey(); + var block_to_portal_block = portal_blocks_in_chunk.computeIfAbsent(chunk_key, k -> new HashMap<>()); + + block_to_portal_block.put(block_key(block), portal_block.lookup(portal.id())); + } + + public PortalBlockLookup portal_block_for(final Block block) { + final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(block.getWorld().getUID()); + if (portal_blocks_in_chunk == null) { + return null; + } + + final var chunk_key = block.getChunk().getChunkKey(); + final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); + if (block_to_portal_block == null) { + return null; + } + + return block_to_portal_block.get(block_key(block)); + } + + public Portal portal_for(@Nullable final UUID uuid) { + final var portal = portals.get(uuid); + if (portal == null || !portal.spawn().isWorldLoaded()) { + return null; + } + return portal; + } + + public Portal portal_for(@NotNull final PortalBlockLookup block) { + return portal_for(block.portal_id()); + } + + public Portal portal_for(final Block block) { + final var portal_block = portal_block_for(block); + if (portal_block == null) { + return null; + } + + return portal_for(portal_block); + } + + public boolean is_portal_block(final Block block) { + final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(block.getWorld().getUID()); + if (portal_blocks_in_chunk == null) { + return false; + } + + final var chunk_key = block.getChunk().getChunkKey(); + final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); + if (block_to_portal_block == null) { + return false; + } + + return block_to_portal_block.containsKey(block_key(block)); + } + + public Portal controlled_portal(final Block block) { + final var root_portal = portal_for(block); + if (root_portal != null) { + return root_portal; + } + + // Find adjacent console blocks in full 3x3x3 cube, which will make this block a + // controlling block + for (final var adj : adjacent_blocks_3d(block)) { + final var portal_block = portal_block_for(adj); + if (portal_block != null && portal_block.type() == PortalBlock.Type.CONSOLE) { + return portal_for(portal_block); + } + } + + return null; + } + + public Set chunks_for(final Portal portal) { + if (portal == null) { + return new HashSet(); + } + + final var set = new HashSet(); + for (final var pb : portal.blocks()) { + set.add(pb.block().getChunk()); + } + return set; + } + + public void load_portal_chunks(final Portal portal) { + // Load chunks and adds a ticket, so they get loaded and are kept loaded + for (final var chunk : chunks_for(portal)) { + final var chunk_key = chunk.getChunkKey(); + final var ticket_counter = chunk_ticket_count.get(chunk_key); + if (ticket_counter == null) { + chunk.addPluginChunkTicket(this); + chunk_ticket_count.put(chunk_key, 1); + } else { + chunk_ticket_count.put(chunk_key, ticket_counter + 1); + } + } + } + + public void allow_unload_portal_chunks(final Portal portal) { + // Removes the ticket so chunks can be unloaded again + for (final var chunk : chunks_for(portal)) { + final var chunk_key = chunk.getChunkKey(); + final var ticket_counter = chunk_ticket_count.get(chunk_key); + + if (ticket_counter > 1) { + chunk_ticket_count.put(chunk_key, ticket_counter - 1); + } else if (ticket_counter == 1) { + chunk.removePluginChunkTicket(this); + chunk_ticket_count.remove(chunk_key); + } + } + } + + public void connect_portals(final Portal src, final Portal dst) { + // Load chunks + load_portal_chunks(src); + load_portal_chunks(dst); + + // Add to map + connected_portals.put(src.id(), dst.id()); + connected_portals.put(dst.id(), src.id()); + + // Activate both + src.on_connect(this, dst); + dst.on_connect(this, src); + + // Schedule automatic disable + start_disable_task(src, dst); + } + + public void disconnect_portals(final Portal src) { + disconnect_portals(src, portal_for(connected_portals.get(src.id()))); + } + + public void disconnect_portals(final Portal src, final Portal dst) { + if (src == null || dst == null) { + return; + } + + // Allow unloading chunks again + allow_unload_portal_chunks(src); + allow_unload_portal_chunks(dst); + + // Remove from a map + connected_portals.remove(src.id()); + connected_portals.remove(dst.id()); + + // Deactivate both + src.on_disconnect(this, dst); + dst.on_disconnect(this, src); + + // Reset target id's if the target portal was transient and + // the target isn't locked. + if (dst.visibility().is_transient_target() && !src.target_locked()) { + src.target_id(null); + src.update_blocks(this); + } + + // Remove an automatic disable task if existing + stop_disable_task(src, dst); + } + + private void start_disable_task(final Portal portal, final Portal target) { + stop_disable_task(portal, target); + final var task = schedule_task( + new PortalDisableRunnable(portal, target), + ms_to_ticks(config_deactivation_delay) + ); + disable_tasks.put(portal.id(), task); + disable_tasks.put(target.id(), task); + } + + private void stop_disable_task(final Portal portal, final Portal target) { + final var task1 = disable_tasks.remove(portal.id()); + final var task2 = disable_tasks.remove(target.id()); + if (task1 != null) { + task1.cancel(); + } + if (task2 != null && task2 != task1) { + task2.cancel(); + } + } + + @Override + public void on_disable() { + // Disable all portals now + for (final var id : new ArrayList<>(connected_portals.keySet())) { + disconnect_portals(portal_for(id)); + } + + // Remove all console items, and all chunk tickets + chunk_ticket_count.clear(); + for (final var world : getServer().getWorlds()) { + for (final var chunk : world.getLoadedChunks()) { + // Remove console item + for_each_console_block_in_chunk(chunk, (block, console) -> remove_console_item(block)); + // Allow chunk unloading + chunk.removePluginChunkTicket(this); + } + } + + // Save data + update_persistent_data(); + super.on_disable(); + } + + public boolean is_activated(final Portal portal) { + return connected_portals.containsKey(portal.id()); + } + + public Portal connected_portal(final Portal portal) { + final var connected_id = connected_portals.get(portal.id()); + if (connected_id == null) { + return null; + } + return portal_for(connected_id); + } + + public ItemStack icon_for(final Portal portal) { + final var item = portal.icon(); + if (item == null) { + return config_default_icon.item(); + } else { + return item; + } + } + + private ItemStack make_console_item(final Portal portal, boolean active) { + final Portal target; + if (active) { + target = connected_portal(portal); + } else { + target = portal.target(this); + } + + // Try to use target portal's block + ItemStack item = null; + if (target != null) { + item = target.icon(); + } + + // Fallback item + if (item == null) { + item = config_default_icon.item(); + } + + final var target_name = target == null ? lang_console_no_target.str() : target.name(); + final Component display_name; + if (active) { + display_name = lang_console_display_active.format("§5" + target_name); + } else { + display_name = lang_console_display_inactive.format("§7" + target_name); + } + + return name_item(item, display_name); + } + + public void update_portal_icon(final Portal portal) { + // Update map marker, as name could have changed + update_marker(portal); + + for (final var active_console : console_floating_items.keySet()) { + final var portal_block = portal_block_for(active_console); + final var other = portal_for(portal_block); + if (Objects.equals(other.target_id(), portal.id())) { + update_console_item(other, active_console); + } + } + } + + public void update_portal_visibility(final Portal portal) { + // Replace references to the portal everywhere if visibility + // has changed. + switch (portal.visibility()) { + case PRIVATE: + case GROUP: + // Not visible from outside, these are transient. + for (final var other : portals.values()) { + if (Objects.equals(other.target_id(), portal.id())) { + other.target_id(null); + } + } + break; + case GROUP_INTERNAL: + // Remove from portals outside the group + for (final var other : portals.values()) { + if (Objects.equals(other.target_id(), portal.id()) && !is_in_same_region_group(other, portal)) { + other.target_id(null); + } + } + break; + default: // Nothing to do + break; + } + + // Update map marker + update_marker(portal); + } + + public void update_console_item(final Portal portal, final Block block) { + var console_item = console_floating_items.get(block); + final boolean is_new; + if (console_item == null) { + console_item = new FloatingItem( + block.getWorld(), + block.getX() + 0.5, + block.getY() + 1.2, + block.getZ() + 0.5 + ); + is_new = true; + } else { + is_new = false; + } + + final var active = is_activated(portal); + console_item.setItem(item_handle(make_console_item(portal, active))); + + if (is_new) { + console_floating_items.put(block, console_item); + spawn(block.getWorld(), console_item); + } + } + + public void remove_console_item(final Block block) { + final var console_item = console_floating_items.remove(block); + if (console_item != null) { + console_item.discard(); + } + } + + private void for_each_console_block_in_chunk( + final Chunk chunk, + final Consumer2 consumer + ) { + final var portal_blocks_in_chunk = portal_blocks_in_chunk_in_world.get(chunk.getWorld().getUID()); + if (portal_blocks_in_chunk == null) { + return; + } + + final var chunk_key = chunk.getChunkKey(); + final var block_to_portal_block = portal_blocks_in_chunk.get(chunk_key); + if (block_to_portal_block == null) { + return; + } + + block_to_portal_block.forEach((k, v) -> { + if (v.type() == PortalBlock.Type.CONSOLE) { + consumer.apply(unpack_block_key(chunk, k), v); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_monitor_chunk_unload(final ChunkUnloadEvent event) { + final var chunk = event.getChunk(); + + // Disable all consoles in this chunk + for_each_console_block_in_chunk(chunk, (block, console) -> remove_console_item(block)); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on_monitor_chunk_load(final ChunkLoadEvent event) { + final var chunk = event.getChunk(); + + // Enable all consoles in this chunk + for_each_console_block_in_chunk(chunk, (block, console) -> { + final var portal = portal_for(console.portal_id()); + update_console_item(portal, block); + }); + } + + public void update_marker(final Portal portal) { + dynmap_layer.update_marker(portal); + blue_map_layer.update_marker(portal); + } + + public void remove_marker(final UUID portal_id) { + dynmap_layer.remove_marker(portal_id); + blue_map_layer.remove_marker(portal_id); + } + + @EventHandler + public void on_save_world(final WorldSaveEvent event) { + update_persistent_data(event.getWorld()); + } + + @EventHandler + public void on_load_world(final WorldLoadEvent event) { + load_persistent_data(event.getWorld()); + } + + @EventHandler + public void on_unload_world(final WorldUnloadEvent event) { + // Save data before unloading a world (not called on stop) + update_persistent_data(event.getWorld()); + } + + public void update_persistent_data() { + for (final var world : getServer().getWorlds()) { + update_persistent_data(world); + } + } + + public static final NamespacedKey STORAGE_PORTALS = StorageUtil.namespaced_key("vane_portals", "portals"); + + public void load_persistent_data(final World world) { + final var data = world.getPersistentDataContainer(); + final var storage_portal_prefix = STORAGE_PORTALS + "."; + + // Load all currently stored portals. + final var pdc_portals = data + .getKeys() + .stream() + .filter(key -> key.toString().startsWith(storage_portal_prefix)) + .map(key -> StringUtils.removeStart(key.toString(), storage_portal_prefix)) + .map(uuid -> UUID.fromString(uuid)) + .collect(Collectors.toSet()); + + for (final var portal_id : pdc_portals) { + final var json_bytes = data.get( + NamespacedKey.fromString(storage_portal_prefix + portal_id.toString()), + PersistentDataType.BYTE_ARRAY + ); + try { + final var portal = PersistentSerializer.from_json(Portal.class, new JSONObject(new String(json_bytes))); + index_portal(portal); + } catch (IOException e) { + log.log(Level.SEVERE, "error while serializing persistent data!", e); + } + } + log.log( + Level.INFO, + "Loaded " + pdc_portals.size() + " portals for world " + world.getName() + "(" + world.getUID() + ")" + ); + + // Convert portals from legacy storage + final Set remove_from_legacy_storage = new HashSet<>(); + int converted = 0; + for (final var portal : storage_portals.values()) { + if (!portal.spawn_world().equals(world.getUID())) { + continue; + } + + if (portals.containsKey(portal.id())) { + remove_from_legacy_storage.add(portal.id()); + continue; + } + + index_portal(portal); + portal.invalidated = true; + converted += 1; + } + + // Remove any portal that was successfully loaded from the new storage. + remove_from_legacy_storage.forEach(storage_portals::remove); + if (remove_from_legacy_storage.size() > 0) { + mark_persistent_storage_dirty(); + } + + // Update all consoles in the loaded world. These + // might be missed by chunk load event as it runs asynchronous + // to this function, and it can't be synchronized without annoying the server. + for (final var chunk : world.getLoadedChunks()) { + for_each_console_block_in_chunk(chunk, (block, console) -> { + final var portal = portal_for(console.portal_id()); + update_console_item(portal, block); + }); + } + + // Save if we had any conversions + if (converted > 0) { + update_persistent_data(); + } + } + + public void update_persistent_data(final World world) { + final var data = world.getPersistentDataContainer(); + final var storage_portal_prefix = STORAGE_PORTALS + "."; + + // Update invalidated portals + portals + .values() + .stream() + .filter(x -> x.invalidated && x.spawn_world().equals(world.getUID())) + .forEach(portal -> { + try { + final var json = PersistentSerializer.to_json(Portal.class, portal); + data.set( + NamespacedKey.fromString(storage_portal_prefix + portal.id().toString()), + PersistentDataType.BYTE_ARRAY, + json.toString().getBytes() + ); + } catch (IOException e) { + log.log(Level.SEVERE, "error while serializing persistent data!", e); + return; + } + + portal.invalidated = false; + }); + + // Get all currently stored portals. + final var stored_portals = data + .getKeys() + .stream() + .filter(key -> key.toString().startsWith(storage_portal_prefix)) + .map(key -> StringUtils.removeStart(key.toString(), storage_portal_prefix)) + .map(uuid -> UUID.fromString(uuid)) + .collect(Collectors.toSet()); + + // Remove all portals that no longer exist + Sets.difference(stored_portals, portals.keySet()).forEach(id -> + data.remove(NamespacedKey.fromString(storage_portal_prefix + id.toString())) + ); + } + + private class PortalDisableRunnable implements Runnable { + + private Portal src; + private Portal dst; + + public PortalDisableRunnable(final Portal src, final Portal dst) { + this.src = src; + this.dst = dst; + } + + @Override + public void run() { + Portals.this.disconnect_portals(src, dst); + } + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/entity/FloatingItem.java b/vane-portals/src/main/java/org/oddlama/vane/portals/entity/FloatingItem.java index dd16d93a4..d9344346e 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/entity/FloatingItem.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/entity/FloatingItem.java @@ -3,7 +3,6 @@ import static org.oddlama.vane.util.Nms.world_handle; import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; @@ -13,90 +12,91 @@ public class FloatingItem extends ItemEntity { - public FloatingItem(final Location location) { - this(location.getWorld(), location.getX(), location.getY(), location.getZ()); - } - - public FloatingItem(final World world, double x, double y, double z) { - this(EntityType.ITEM, world_handle(world)); - setPos(x, y, z); - } - - public FloatingItem(EntityType entitytypes, Level world) { - super(entitytypes, world); - setSilent(true); - setInvulnerable(true); - setNoGravity(true); - //setSneaking(true); // Names would then only visible on direct line of sight BUT much darker and offset by -0.5 in y direction - setNeverPickUp(); - setUnlimitedLifetime(); - persist = false; - noPhysics = true; - } - - @Override - public boolean isAlive() { - // Required to efficiently prevent hoppers and hopper minecarts from picking this up - return false; - } - - @Override - public boolean isAttackable() { - return false; - } - - @Override - public boolean isCollidable(boolean ignoreClimbing) { - return false; - } - - @Override - public boolean isInvisible() { - return true; - } - - @Override - public boolean fireImmune() { - return true; - } - - @Override - public void tick() {} - - @Override - public void inactiveTick() {} - - // Don't save or load - @Override - public void readAdditionalSaveData(CompoundTag nbt) {} - - @Override - public void addAdditionalSaveData(CompoundTag nbt) {} - - @Override - public boolean serializeEntity(CompoundTag nbt) { - return false; - } - - @Override - public boolean save(CompoundTag nbt) { - return false; - } - - @Override - public CompoundTag saveWithoutId(CompoundTag nbt) { - return nbt; - } - - @Override - public void load(CompoundTag nbt) {} - - @Override - public void setItem(ItemStack itemStack) { - super.setItem(itemStack); - if (itemStack.getHoverName().toFlatList().size() > 0) { - setCustomNameVisible(true); - setCustomName(itemStack.getHoverName()); - } else setCustomNameVisible(false); - } + public FloatingItem(final Location location) { + this(location.getWorld(), location.getX(), location.getY(), location.getZ()); + } + + public FloatingItem(final World world, double x, double y, double z) { + this(EntityType.ITEM, world_handle(world)); + setPos(x, y, z); + } + + public FloatingItem(EntityType entitytypes, Level world) { + super(entitytypes, world); + setSilent(true); + setInvulnerable(true); + setNoGravity(true); + // setSneaking(true); // Names would then only visible on direct line of sight BUT much + // darker and offset by -0.5 in y direction + setNeverPickUp(); + setUnlimitedLifetime(); + persist = false; + noPhysics = true; + } + + @Override + public boolean isAlive() { + // Required to efficiently prevent hoppers and hopper minecarts from picking this up + return false; + } + + @Override + public boolean isAttackable() { + return false; + } + + @Override + public boolean isCollidable(boolean ignoreClimbing) { + return false; + } + + @Override + public boolean isInvisible() { + return true; + } + + @Override + public boolean fireImmune() { + return true; + } + + @Override + public void tick() {} + + @Override + public void inactiveTick() {} + + // Don't save or load + @Override + public void readAdditionalSaveData(CompoundTag nbt) {} + + @Override + public void addAdditionalSaveData(CompoundTag nbt) {} + + @Override + public boolean serializeEntity(CompoundTag nbt) { + return false; + } + + @Override + public boolean save(CompoundTag nbt) { + return false; + } + + @Override + public CompoundTag saveWithoutId(CompoundTag nbt) { + return nbt; + } + + @Override + public void load(CompoundTag nbt) {} + + @Override + public void setItem(ItemStack itemStack) { + super.setItem(itemStack); + if (itemStack.getHoverName().toFlatList().size() > 0) { + setCustomNameVisible(true); + setCustomName(itemStack.getHoverName()); + } else setCustomNameVisible(false); + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/EntityMoveEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/EntityMoveEvent.java index 436f8a292..b88969ff7 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/EntityMoveEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/EntityMoveEvent.java @@ -6,34 +6,35 @@ import org.bukkit.event.HandlerList; public class EntityMoveEvent extends Event { - private static final HandlerList handlers = new HandlerList(); - private Entity entity; - private Location from; - private Location to; - - public EntityMoveEvent(final Entity entity, final Location from, final Location to) { - this.entity = entity; - this.from = from; - this.to = to; - } - - public Entity getEntity() { - return entity; - } - - public Location getFrom() { - return from; - } - - public Location getTo() { - return to; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } + + private static final HandlerList handlers = new HandlerList(); + private Entity entity; + private Location from; + private Location to; + + public EntityMoveEvent(final Entity entity, final Location from, final Location to) { + this.entity = entity; + this.from = from; + this.to = to; + } + + public Entity getEntity() { + return entity; + } + + public Location getFrom() { + return from; + } + + public Location getTo() { + return to; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalActivateEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalActivateEvent.java index 501559479..828f43a1d 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalActivateEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalActivateEvent.java @@ -7,34 +7,34 @@ public class PortalActivateEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Portal portal; - private Portal target; - - public PortalActivateEvent(@Nullable final Player player, final Portal portal, final Portal target) { - this.player = player; - this.portal = portal; - this.target = target; - } - - public @Nullable Player getPlayer() { - return player; - } - - public Portal getPortal() { - return portal; - } - - public Portal getTarget() { - return target; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Portal portal; + private Portal target; + + public PortalActivateEvent(@Nullable final Player player, final Portal portal, final Portal target) { + this.player = player; + this.portal = portal; + this.target = target; + } + + public @Nullable Player getPlayer() { + return player; + } + + public Portal getPortal() { + return portal; + } + + public Portal getTarget() { + return target; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalChangeSettingsEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalChangeSettingsEvent.java index eaabd2227..40a4d1bdd 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalChangeSettingsEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalChangeSettingsEvent.java @@ -6,48 +6,48 @@ public class PortalChangeSettingsEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Portal portal; - private boolean check_only; - private boolean cancel_if_not_owner = true; - - public PortalChangeSettingsEvent(final Player player, final Portal portal, boolean check_only) { - this.player = player; - this.portal = portal; - this.check_only = check_only; - } - - public void setCancelIfNotOwner(boolean cancel_if_not_owner) { - this.cancel_if_not_owner = cancel_if_not_owner; - } - - public Player getPlayer() { - return player; - } - - public Portal getPortal() { - return portal; - } - - public boolean checkOnly() { - return check_only; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } - - @Override - public boolean isCancelled() { - var cancelled = super.isCancelled(); - if (cancel_if_not_owner) { - cancelled |= !player.getUniqueId().equals(portal.owner()); - } - return cancelled; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Portal portal; + private boolean check_only; + private boolean cancel_if_not_owner = true; + + public PortalChangeSettingsEvent(final Player player, final Portal portal, boolean check_only) { + this.player = player; + this.portal = portal; + this.check_only = check_only; + } + + public void setCancelIfNotOwner(boolean cancel_if_not_owner) { + this.cancel_if_not_owner = cancel_if_not_owner; + } + + public Player getPlayer() { + return player; + } + + public Portal getPortal() { + return portal; + } + + public boolean checkOnly() { + return check_only; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + var cancelled = super.isCancelled(); + if (cancel_if_not_owner) { + cancelled |= !player.getUniqueId().equals(portal.owner()); + } + return cancelled; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalConstructEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalConstructEvent.java index 73e23253b..f349a760c 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalConstructEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalConstructEvent.java @@ -6,34 +6,34 @@ public class PortalConstructEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private PortalBoundary boundary; - private boolean check_only; - - public PortalConstructEvent(final Player player, final PortalBoundary boundary, boolean check_only) { - this.player = player; - this.boundary = boundary; - this.check_only = check_only; - } - - public Player getPlayer() { - return player; - } - - public PortalBoundary getBoundary() { - return boundary; - } - - public boolean checkOnly() { - return check_only; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private PortalBoundary boundary; + private boolean check_only; + + public PortalConstructEvent(final Player player, final PortalBoundary boundary, boolean check_only) { + this.player = player; + this.boundary = boundary; + this.check_only = check_only; + } + + public Player getPlayer() { + return player; + } + + public PortalBoundary getBoundary() { + return boundary; + } + + public boolean checkOnly() { + return check_only; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDeactivateEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDeactivateEvent.java index f6d62d6b4..866ed3814 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDeactivateEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDeactivateEvent.java @@ -7,28 +7,28 @@ public class PortalDeactivateEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Portal portal; - - public PortalDeactivateEvent(final Player player, final Portal portal) { - this.player = player; - this.portal = portal; - } - - public @Nullable Player getPlayer() { - return player; - } - - public Portal getPortal() { - return portal; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Portal portal; + + public PortalDeactivateEvent(final Player player, final Portal portal) { + this.player = player; + this.portal = portal; + } + + public @Nullable Player getPlayer() { + return player; + } + + public Portal getPortal() { + return portal; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDestroyEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDestroyEvent.java index 62b660b39..3307b2367 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDestroyEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalDestroyEvent.java @@ -6,48 +6,48 @@ public class PortalDestroyEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Portal portal; - private boolean check_only; - private boolean cancel_if_not_owner = true; - - public PortalDestroyEvent(final Player player, final Portal portal, boolean check_only) { - this.player = player; - this.portal = portal; - this.check_only = check_only; - } - - public void setCancelIfNotOwner(boolean cancel_if_not_owner) { - this.cancel_if_not_owner = cancel_if_not_owner; - } - - public Player getPlayer() { - return player; - } - - public Portal getPortal() { - return portal; - } - - public boolean checkOnly() { - return check_only; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } - - @Override - public boolean isCancelled() { - var cancelled = super.isCancelled(); - if (cancel_if_not_owner) { - cancelled |= !player.getUniqueId().equals(portal.owner()); - } - return cancelled; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Portal portal; + private boolean check_only; + private boolean cancel_if_not_owner = true; + + public PortalDestroyEvent(final Player player, final Portal portal, boolean check_only) { + this.player = player; + this.portal = portal; + this.check_only = check_only; + } + + public void setCancelIfNotOwner(boolean cancel_if_not_owner) { + this.cancel_if_not_owner = cancel_if_not_owner; + } + + public Player getPlayer() { + return player; + } + + public Portal getPortal() { + return portal; + } + + public boolean checkOnly() { + return check_only; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + var cancelled = super.isCancelled(); + if (cancel_if_not_owner) { + cancelled |= !player.getUniqueId().equals(portal.owner()); + } + return cancelled; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalEvent.java index 0080d391a..e216c6f76 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalEvent.java @@ -5,15 +5,15 @@ public abstract class PortalEvent extends Event implements Cancellable { - private boolean cancelled = false; + private boolean cancelled = false; - @Override - public boolean isCancelled() { - return cancelled; - } + @Override + public boolean isCancelled() { + return cancelled; + } - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalLinkConsoleEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalLinkConsoleEvent.java index a0ff2a736..4921f7bb6 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalLinkConsoleEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalLinkConsoleEvent.java @@ -9,66 +9,66 @@ public class PortalLinkConsoleEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Portal portal; - private Block console; - private List portal_blocks; - private boolean check_only; - private boolean cancel_if_not_owner = true; + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Portal portal; + private Block console; + private List portal_blocks; + private boolean check_only; + private boolean cancel_if_not_owner = true; - public PortalLinkConsoleEvent( - final Player player, - final Block console, - final List portal_blocks, - boolean check_only, - @Nullable final Portal portal - ) { - this.player = player; - this.console = console; - this.portal_blocks = portal_blocks; - this.check_only = check_only; - this.portal = portal; - } + public PortalLinkConsoleEvent( + final Player player, + final Block console, + final List portal_blocks, + boolean check_only, + @Nullable final Portal portal + ) { + this.player = player; + this.console = console; + this.portal_blocks = portal_blocks; + this.check_only = check_only; + this.portal = portal; + } - public void setCancelIfNotOwner(boolean cancel_if_not_owner) { - this.cancel_if_not_owner = cancel_if_not_owner; - } + public void setCancelIfNotOwner(boolean cancel_if_not_owner) { + this.cancel_if_not_owner = cancel_if_not_owner; + } - public Player getPlayer() { - return player; - } + public Player getPlayer() { + return player; + } - public Block getConsole() { - return console; - } + public Block getConsole() { + return console; + } - public List getPortalBlocks() { - return portal_blocks; - } + public List getPortalBlocks() { + return portal_blocks; + } - public boolean checkOnly() { - return check_only; - } + public boolean checkOnly() { + return check_only; + } - public @Nullable Portal getPortal() { - return portal; - } + public @Nullable Portal getPortal() { + return portal; + } - public HandlerList getHandlers() { - return handlers; - } + public HandlerList getHandlers() { + return handlers; + } - public static HandlerList getHandlerList() { - return handlers; - } + public static HandlerList getHandlerList() { + return handlers; + } - @Override - public boolean isCancelled() { - var cancelled = super.isCancelled(); - if (cancel_if_not_owner && portal != null) { - cancelled |= !player.getUniqueId().equals(portal.owner()); - } - return cancelled; - } + @Override + public boolean isCancelled() { + var cancelled = super.isCancelled(); + if (cancel_if_not_owner && portal != null) { + cancelled |= !player.getUniqueId().equals(portal.owner()); + } + return cancelled; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalOpenConsoleEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalOpenConsoleEvent.java index 9bf116e4d..375ad8c22 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalOpenConsoleEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalOpenConsoleEvent.java @@ -7,34 +7,34 @@ public class PortalOpenConsoleEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Block console; - private Portal portal; - - public PortalOpenConsoleEvent(final Player player, final Block console, final Portal portal) { - this.player = player; - this.console = console; - this.portal = portal; - } - - public Player getPlayer() { - return player; - } - - public Block getConsole() { - return console; - } - - public Portal getPortal() { - return portal; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Block console; + private Portal portal; + + public PortalOpenConsoleEvent(final Player player, final Block console, final Portal portal) { + this.player = player; + this.console = console; + this.portal = portal; + } + + public Player getPlayer() { + return player; + } + + public Block getConsole() { + return console; + } + + public Portal getPortal() { + return portal; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalSelectTargetEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalSelectTargetEvent.java index b34c084d4..a0b052f0d 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalSelectTargetEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalSelectTargetEvent.java @@ -7,40 +7,40 @@ public class PortalSelectTargetEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Portal portal; - private Portal target; - private boolean check_only; - - public PortalSelectTargetEvent(final Player player, final Portal portal, final Portal target, boolean check_only) { - this.player = player; - this.portal = portal; - this.target = target; - this.check_only = check_only; - } - - public Player getPlayer() { - return player; - } - - public Portal getPortal() { - return portal; - } - - public @Nullable Portal getTarget() { - return target; - } - - public boolean checkOnly() { - return check_only; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Portal portal; + private Portal target; + private boolean check_only; + + public PortalSelectTargetEvent(final Player player, final Portal portal, final Portal target, boolean check_only) { + this.player = player; + this.portal = portal; + this.target = target; + this.check_only = check_only; + } + + public Player getPlayer() { + return player; + } + + public Portal getPortal() { + return portal; + } + + public @Nullable Portal getTarget() { + return target; + } + + public boolean checkOnly() { + return check_only; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalUnlinkConsoleEvent.java b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalUnlinkConsoleEvent.java index ff49f4221..113e3926f 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalUnlinkConsoleEvent.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/event/PortalUnlinkConsoleEvent.java @@ -7,54 +7,54 @@ public class PortalUnlinkConsoleEvent extends PortalEvent { - private static final HandlerList handlers = new HandlerList(); - private Player player; - private Block console; - private Portal portal; - private boolean check_only; - private boolean cancel_if_not_owner = true; - - public PortalUnlinkConsoleEvent(final Player player, final Block console, final Portal portal, boolean check_only) { - this.player = player; - this.console = console; - this.portal = portal; - this.check_only = check_only; - } - - public void setCancelIfNotOwner(boolean cancel_if_not_owner) { - this.cancel_if_not_owner = cancel_if_not_owner; - } - - public Player getPlayer() { - return player; - } - - public Block getConsole() { - return console; - } - - public Portal getPortal() { - return portal; - } - - public boolean checkOnly() { - return check_only; - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } - - @Override - public boolean isCancelled() { - var cancelled = super.isCancelled(); - if (cancel_if_not_owner) { - cancelled |= !player.getUniqueId().equals(portal.owner()); - } - return cancelled; - } + private static final HandlerList handlers = new HandlerList(); + private Player player; + private Block console; + private Portal portal; + private boolean check_only; + private boolean cancel_if_not_owner = true; + + public PortalUnlinkConsoleEvent(final Player player, final Block console, final Portal portal, boolean check_only) { + this.player = player; + this.console = console; + this.portal = portal; + this.check_only = check_only; + } + + public void setCancelIfNotOwner(boolean cancel_if_not_owner) { + this.cancel_if_not_owner = cancel_if_not_owner; + } + + public Player getPlayer() { + return player; + } + + public Block getConsole() { + return console; + } + + public Portal getPortal() { + return portal; + } + + public boolean checkOnly() { + return check_only; + } + + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + var cancelled = super.isCancelled(); + if (cancel_if_not_owner) { + cancelled |= !player.getUniqueId().equals(portal.owner()); + } + return cancelled; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/ConsoleMenu.java b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/ConsoleMenu.java index 2e8f68122..f550c730c 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/ConsoleMenu.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/ConsoleMenu.java @@ -29,352 +29,334 @@ public class ConsoleMenu extends ModuleComponent { - @LangMessage - public TranslatedMessage lang_title; - - @LangMessage - public TranslatedMessage lang_unlink_console_confirm_title; - - @LangMessage - public TranslatedMessage lang_destroy_portal_confirm_title; - - @LangMessage - public TranslatedMessage lang_select_target_title; - - @LangMessage - public TranslatedMessage lang_filter_portals_title; - - @LangMessage - public TranslatedMessage lang_select_target_portal_visibility_public; - - @LangMessage - public TranslatedMessage lang_select_target_portal_visibility_private; - - @LangMessage - public TranslatedMessage lang_select_target_portal_visibility_group; - - @LangMessage - public TranslatedMessage lang_select_target_portal_visibility_group_internal; - - public TranslatedItemStack item_settings; - public TranslatedItemStack item_select_target; - public TranslatedItemStack item_select_target_portal; - public TranslatedItemStack item_select_target_locked; - public TranslatedItemStack item_unlink_console; - public TranslatedItemStack item_unlink_console_confirm_accept; - public TranslatedItemStack item_unlink_console_confirm_cancel; - public TranslatedItemStack item_destroy_portal; - public TranslatedItemStack item_destroy_portal_confirm_accept; - public TranslatedItemStack item_destroy_portal_confirm_cancel; - - public ConsoleMenu(Context context) { - super(context.namespace("console")); - final var ctx = get_context(); - item_settings = - new TranslatedItemStack<>(ctx, "settings", Material.WRITABLE_BOOK, 1, "Used to enter portal settings."); - item_select_target = - new TranslatedItemStack<>( - ctx, - "select_target", - Material.COMPASS, - 1, - "Used to enter portal target selection." - ); - item_select_target_portal = - new TranslatedItemStack<>( - ctx, - "select_target_portal", - Material.COMPASS, - 1, - "Used to represent a portal in the target selection menu." - ); - item_select_target_locked = - new TranslatedItemStack<>( - ctx, - "select_target_locked", - Material.FIREWORK_STAR, - 1, - "Used to show portal target selection when the target is locked." - ); - item_unlink_console = - new TranslatedItemStack<>( - ctx, - "unlink_console", - StorageUtil.namespaced_key("vane", "decoration_tnt_1"), - 1, - "Used to unlink the current console." - ); - item_unlink_console_confirm_accept = - new TranslatedItemStack<>( - ctx, - "unlink_console_confirm_accept", - StorageUtil.namespaced_key("vane", "decoration_tnt_1"), - 1, - "Used to confirm unlinking the current console." - ); - item_unlink_console_confirm_cancel = - new TranslatedItemStack<>( - ctx, - "unlink_console_confirm_cancel", - Material.PRISMARINE_SHARD, - 1, - "Used to cancel unlinking the current console." - ); - item_destroy_portal = - new TranslatedItemStack<>(ctx, "destroy_portal", Material.TNT, 1, "Used to destroy the portal."); - item_destroy_portal_confirm_accept = - new TranslatedItemStack<>( - ctx, - "destroy_portal_confirm_accept", - Material.TNT, - 1, - "Used to confirm destroying the portal." - ); - item_destroy_portal_confirm_cancel = - new TranslatedItemStack<>( - ctx, - "destroy_portal_confirm_cancel", - Material.PRISMARINE_SHARD, - 1, - "Used to cancel destroying the portal." - ); - } - - public Menu create(final Portal portal, final Player player, final Block console) { - final var columns = 9; - final var title = lang_title.str_component("§5§l" + portal.name()); - final var console_menu = new Menu(get_context(), Bukkit.createInventory(null, columns, title)); - console_menu.tag(new PortalMenuTag(portal.id())); - - // Check if target selection would be allowed - final var select_target_event = new PortalSelectTargetEvent(player, portal, null, true); - get_module().getServer().getPluginManager().callEvent(select_target_event); - if (!select_target_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { - console_menu.add(menu_item_select_target(portal)); - } - - // Check if settings would be allowed - final var settings_event = new PortalChangeSettingsEvent(player, portal, true); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (!settings_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { - console_menu.add(menu_item_settings(portal, console)); - } - - // Check if unlink would be allowed - final var unlink_event = new PortalUnlinkConsoleEvent(player, console, portal, true); - get_module().getServer().getPluginManager().callEvent(unlink_event); - if (!unlink_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { - console_menu.add(menu_item_unlink_console(portal, console)); - } - - // Check if destroy would be allowed - final var destroy_event = new PortalDestroyEvent(player, portal, true); - get_module().getServer().getPluginManager().callEvent(destroy_event); - if (!destroy_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { - console_menu.add(menu_item_destroy_portal(portal)); - } - - return console_menu; - } - - private MenuWidget menu_item_settings(final Portal portal, final Block console) { - return new MenuItem( - 0, - item_settings.item(), - (player, menu, self) -> { - final var settings_event = new PortalChangeSettingsEvent(player, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player); - return ClickResult.ERROR; - } - - menu.close(player); - get_module().menus.settings_menu.create(portal, player, console).open(player); - return ClickResult.SUCCESS; - } - ); - } - - private Component portal_visibility(Portal.Visibility visibility) { - return ( - switch (visibility) { - case PUBLIC -> lang_select_target_portal_visibility_public; - case GROUP -> lang_select_target_portal_visibility_group; - case GROUP_INTERNAL -> lang_select_target_portal_visibility_group_internal; - case PRIVATE -> lang_select_target_portal_visibility_private; - } - ).format(); - } - - private MenuWidget menu_item_select_target(final Portal portal) { - return new MenuItem( - 4, - null, - (player, menu, self) -> { - if (portal.target_locked()) { - return ClickResult.ERROR; - } else { - menu.close(player); - final var all_portals = get_module() - .all_available_portals() - .stream() - .filter(p -> { - switch (p.visibility()) { - case PUBLIC: - return true; - case GROUP: - return get_module().player_can_use_portals_in_region_group_of(player, p); - case GROUP_INTERNAL: - return get_module().is_in_same_region_group(portal, p); - case PRIVATE: - return player.getUniqueId().equals(p.owner()); - } - return false; - }) - .filter(p -> !Objects.equals(p.id(), portal.id())) - .sorted(new Portal.TargetSelectionComparator(player)) - .collect(Collectors.toList()); - - final var filter = new Filter.StringFilter((p, str) -> p.name().toLowerCase().contains(str) - ); - MenuFactory - .generic_selector( - get_context(), - player, - lang_select_target_title.str(), - lang_filter_portals_title.str(), - all_portals, - p -> { - final var dist = p - .spawn() - .toVector() - .setY(0.0) - .distance(player.getLocation().toVector().setY(0.0)); - return item_select_target_portal.alternative( - get_module().icon_for(p), - "§a§l" + p.name(), - "§6" + String.format("%.1f", dist), - "§b" + p.spawn().getWorld().getName(), - portal_visibility(p.visibility()) - ); - }, - filter, - (player2, m, t) -> { - m.close(player2); - - final var select_target_event = new PortalSelectTargetEvent(player2, portal, t, false); - get_module().getServer().getPluginManager().callEvent(select_target_event); - if ( - select_target_event.isCancelled() && - !player2.hasPermission(get_module().admin_permission) - ) { - get_module().lang_select_target_restricted.send(player2); - return ClickResult.ERROR; - } - - portal.target_id(t.id()); - - // Update portal block to reflect new target on consoles - portal.update_blocks(get_module()); - return ClickResult.SUCCESS; - }, - player2 -> menu.open(player2) - ) - .tag(new PortalMenuTag(portal.id())) - .open(player); - return ClickResult.SUCCESS; - } - } - ) { - @Override - public void item(final ItemStack item) { - final var target = portal.target(get_module()); - final var target_name = "§a" + (target == null ? "None" : target.name()); - if (portal.target_locked()) { - super.item(item_select_target_locked.item(target_name)); - } else { - super.item(item_select_target.item(target_name)); - } - } - }; - } - - private MenuWidget menu_item_unlink_console(final Portal portal, final Block console) { - return new MenuItem( - 7, - item_unlink_console.item(), - (player, menu, self) -> { - menu.close(player); - MenuFactory - .confirm( - get_context(), - lang_unlink_console_confirm_title.str(), - item_unlink_console_confirm_accept.item(), - player2 -> { - // Call event - final var event = new PortalUnlinkConsoleEvent(player2, console, portal, false); - get_module().getServer().getPluginManager().callEvent(event); - if (event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { - get_module().lang_unlink_restricted.send(player2); - return ClickResult.ERROR; - } - - final var portal_block = portal.portal_block_for(console); - if (portal_block == null) { - // The Console was likely already removed by another player - return ClickResult.ERROR; - } - - get_module().remove_portal_block(portal, portal_block); - return ClickResult.SUCCESS; - }, - item_unlink_console_confirm_cancel.item(), - player2 -> menu.open(player2) - ) - .tag(new PortalMenuTag(portal.id())) - .open(player); - return ClickResult.SUCCESS; - } - ); - } - - private MenuWidget menu_item_destroy_portal(final Portal portal) { - return new MenuItem( - 8, - item_destroy_portal.item(), - (player, menu, self) -> { - menu.close(player); - MenuFactory - .confirm( - get_context(), - lang_destroy_portal_confirm_title.str(), - item_destroy_portal_confirm_accept.item(), - player2 -> { - // Call event - final var event = new PortalDestroyEvent(player2, portal, false); - get_module().getServer().getPluginManager().callEvent(event); - if (event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { - get_module().lang_destroy_restricted.send(player2); - return ClickResult.ERROR; - } - - get_module().remove_portal(portal); - return ClickResult.SUCCESS; - }, - item_destroy_portal_confirm_cancel.item(), - player2 -> menu.open(player2) - ) - .tag(new PortalMenuTag(portal.id())) - .open(player); - return ClickResult.SUCCESS; - } - ); - } - - @Override - public void on_enable() {} - - @Override - public void on_disable() {} + @LangMessage + public TranslatedMessage lang_title; + + @LangMessage + public TranslatedMessage lang_unlink_console_confirm_title; + + @LangMessage + public TranslatedMessage lang_destroy_portal_confirm_title; + + @LangMessage + public TranslatedMessage lang_select_target_title; + + @LangMessage + public TranslatedMessage lang_filter_portals_title; + + @LangMessage + public TranslatedMessage lang_select_target_portal_visibility_public; + + @LangMessage + public TranslatedMessage lang_select_target_portal_visibility_private; + + @LangMessage + public TranslatedMessage lang_select_target_portal_visibility_group; + + @LangMessage + public TranslatedMessage lang_select_target_portal_visibility_group_internal; + + public TranslatedItemStack item_settings; + public TranslatedItemStack item_select_target; + public TranslatedItemStack item_select_target_portal; + public TranslatedItemStack item_select_target_locked; + public TranslatedItemStack item_unlink_console; + public TranslatedItemStack item_unlink_console_confirm_accept; + public TranslatedItemStack item_unlink_console_confirm_cancel; + public TranslatedItemStack item_destroy_portal; + public TranslatedItemStack item_destroy_portal_confirm_accept; + public TranslatedItemStack item_destroy_portal_confirm_cancel; + + public ConsoleMenu(Context context) { + super(context.namespace("console")); + final var ctx = get_context(); + item_settings = new TranslatedItemStack<>( + ctx, + "settings", + Material.WRITABLE_BOOK, + 1, + "Used to enter portal settings." + ); + item_select_target = new TranslatedItemStack<>( + ctx, + "select_target", + Material.COMPASS, + 1, + "Used to enter portal target selection." + ); + item_select_target_portal = new TranslatedItemStack<>( + ctx, + "select_target_portal", + Material.COMPASS, + 1, + "Used to represent a portal in the target selection menu." + ); + item_select_target_locked = new TranslatedItemStack<>( + ctx, + "select_target_locked", + Material.FIREWORK_STAR, + 1, + "Used to show portal target selection when the target is locked." + ); + item_unlink_console = new TranslatedItemStack<>( + ctx, + "unlink_console", + StorageUtil.namespaced_key("vane", "decoration_tnt_1"), + 1, + "Used to unlink the current console." + ); + item_unlink_console_confirm_accept = new TranslatedItemStack<>( + ctx, + "unlink_console_confirm_accept", + StorageUtil.namespaced_key("vane", "decoration_tnt_1"), + 1, + "Used to confirm unlinking the current console." + ); + item_unlink_console_confirm_cancel = new TranslatedItemStack<>( + ctx, + "unlink_console_confirm_cancel", + Material.PRISMARINE_SHARD, + 1, + "Used to cancel unlinking the current console." + ); + item_destroy_portal = new TranslatedItemStack<>( + ctx, + "destroy_portal", + Material.TNT, + 1, + "Used to destroy the portal." + ); + item_destroy_portal_confirm_accept = new TranslatedItemStack<>( + ctx, + "destroy_portal_confirm_accept", + Material.TNT, + 1, + "Used to confirm destroying the portal." + ); + item_destroy_portal_confirm_cancel = new TranslatedItemStack<>( + ctx, + "destroy_portal_confirm_cancel", + Material.PRISMARINE_SHARD, + 1, + "Used to cancel destroying the portal." + ); + } + + public Menu create(final Portal portal, final Player player, final Block console) { + final var columns = 9; + final var title = lang_title.str_component("§5§l" + portal.name()); + final var console_menu = new Menu(get_context(), Bukkit.createInventory(null, columns, title)); + console_menu.tag(new PortalMenuTag(portal.id())); + + // Check if target selection would be allowed + final var select_target_event = new PortalSelectTargetEvent(player, portal, null, true); + get_module().getServer().getPluginManager().callEvent(select_target_event); + if (!select_target_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { + console_menu.add(menu_item_select_target(portal)); + } + + // Check if settings would be allowed + final var settings_event = new PortalChangeSettingsEvent(player, portal, true); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (!settings_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { + console_menu.add(menu_item_settings(portal, console)); + } + + // Check if unlink would be allowed + final var unlink_event = new PortalUnlinkConsoleEvent(player, console, portal, true); + get_module().getServer().getPluginManager().callEvent(unlink_event); + if (!unlink_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { + console_menu.add(menu_item_unlink_console(portal, console)); + } + + // Check if destroy would be allowed + final var destroy_event = new PortalDestroyEvent(player, portal, true); + get_module().getServer().getPluginManager().callEvent(destroy_event); + if (!destroy_event.isCancelled() || player.hasPermission(get_module().admin_permission)) { + console_menu.add(menu_item_destroy_portal(portal)); + } + + return console_menu; + } + + private MenuWidget menu_item_settings(final Portal portal, final Block console) { + return new MenuItem(0, item_settings.item(), (player, menu, self) -> { + final var settings_event = new PortalChangeSettingsEvent(player, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player); + return ClickResult.ERROR; + } + + menu.close(player); + get_module().menus.settings_menu.create(portal, player, console).open(player); + return ClickResult.SUCCESS; + }); + } + + private Component portal_visibility(Portal.Visibility visibility) { + return ( + switch (visibility) { + case PUBLIC -> lang_select_target_portal_visibility_public; + case GROUP -> lang_select_target_portal_visibility_group; + case GROUP_INTERNAL -> lang_select_target_portal_visibility_group_internal; + case PRIVATE -> lang_select_target_portal_visibility_private; + } + ).format(); + } + + private MenuWidget menu_item_select_target(final Portal portal) { + return new MenuItem(4, null, (player, menu, self) -> { + if (portal.target_locked()) { + return ClickResult.ERROR; + } else { + menu.close(player); + final var all_portals = get_module() + .all_available_portals() + .stream() + .filter(p -> { + switch (p.visibility()) { + case PUBLIC: + return true; + case GROUP: + return get_module().player_can_use_portals_in_region_group_of(player, p); + case GROUP_INTERNAL: + return get_module().is_in_same_region_group(portal, p); + case PRIVATE: + return player.getUniqueId().equals(p.owner()); + } + return false; + }) + .filter(p -> !Objects.equals(p.id(), portal.id())) + .sorted(new Portal.TargetSelectionComparator(player)) + .collect(Collectors.toList()); + + final var filter = new Filter.StringFilter((p, str) -> p.name().toLowerCase().contains(str)); + MenuFactory.generic_selector( + get_context(), + player, + lang_select_target_title.str(), + lang_filter_portals_title.str(), + all_portals, + p -> { + final var dist = p + .spawn() + .toVector() + .setY(0.0) + .distance(player.getLocation().toVector().setY(0.0)); + return item_select_target_portal.alternative( + get_module().icon_for(p), + "§a§l" + p.name(), + "§6" + String.format("%.1f", dist), + "§b" + p.spawn().getWorld().getName(), + portal_visibility(p.visibility()) + ); + }, + filter, + (player2, m, t) -> { + m.close(player2); + + final var select_target_event = new PortalSelectTargetEvent(player2, portal, t, false); + get_module().getServer().getPluginManager().callEvent(select_target_event); + if ( + select_target_event.isCancelled() && !player2.hasPermission(get_module().admin_permission) + ) { + get_module().lang_select_target_restricted.send(player2); + return ClickResult.ERROR; + } + + portal.target_id(t.id()); + + // Update portal block to reflect new target on consoles + portal.update_blocks(get_module()); + return ClickResult.SUCCESS; + }, + player2 -> menu.open(player2) + ) + .tag(new PortalMenuTag(portal.id())) + .open(player); + return ClickResult.SUCCESS; + } + }) { + @Override + public void item(final ItemStack item) { + final var target = portal.target(get_module()); + final var target_name = "§a" + (target == null ? "None" : target.name()); + if (portal.target_locked()) { + super.item(item_select_target_locked.item(target_name)); + } else { + super.item(item_select_target.item(target_name)); + } + } + }; + } + + private MenuWidget menu_item_unlink_console(final Portal portal, final Block console) { + return new MenuItem(7, item_unlink_console.item(), (player, menu, self) -> { + menu.close(player); + MenuFactory.confirm( + get_context(), + lang_unlink_console_confirm_title.str(), + item_unlink_console_confirm_accept.item(), + player2 -> { + // Call event + final var event = new PortalUnlinkConsoleEvent(player2, console, portal, false); + get_module().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { + get_module().lang_unlink_restricted.send(player2); + return ClickResult.ERROR; + } + + final var portal_block = portal.portal_block_for(console); + if (portal_block == null) { + // The Console was likely already removed by another + // player + return ClickResult.ERROR; + } + + get_module().remove_portal_block(portal, portal_block); + return ClickResult.SUCCESS; + }, + item_unlink_console_confirm_cancel.item(), + player2 -> menu.open(player2) + ) + .tag(new PortalMenuTag(portal.id())) + .open(player); + return ClickResult.SUCCESS; + }); + } + + private MenuWidget menu_item_destroy_portal(final Portal portal) { + return new MenuItem(8, item_destroy_portal.item(), (player, menu, self) -> { + menu.close(player); + MenuFactory.confirm( + get_context(), + lang_destroy_portal_confirm_title.str(), + item_destroy_portal_confirm_accept.item(), + player2 -> { + // Call event + final var event = new PortalDestroyEvent(player2, portal, false); + get_module().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { + get_module().lang_destroy_restricted.send(player2); + return ClickResult.ERROR; + } + + get_module().remove_portal(portal); + return ClickResult.SUCCESS; + }, + item_destroy_portal_confirm_cancel.item(), + player2 -> menu.open(player2) + ) + .tag(new PortalMenuTag(portal.id())) + .open(player); + return ClickResult.SUCCESS; + }); + } + + @Override + public void on_enable() {} + + @Override + public void on_disable() {} } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/EnterNameMenu.java b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/EnterNameMenu.java index f259b03a9..27e24591e 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/EnterNameMenu.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/EnterNameMenu.java @@ -16,41 +16,41 @@ public class EnterNameMenu extends ModuleComponent { - @LangMessage - public TranslatedMessage lang_title; - - @ConfigMaterial(def = Material.ENDER_PEARL, desc = "The item used to name portals.") - public Material config_material; - - public EnterNameMenu(Context context) { - super(context.namespace("enter_name")); - } - - public Menu create(final Player player, final Function2 on_click) { - return create(player, "Name", on_click); - } - - public Menu create( - final Player player, - final String default_name, - final Function2 on_click - ) { - return MenuFactory.anvil_string_input( - get_context(), - player, - lang_title.str(), - new ItemStack(config_material), - default_name, - (p, menu, name) -> { - menu.close(p); - return on_click.apply(p, name); - } - ); - } - - @Override - public void on_enable() {} - - @Override - public void on_disable() {} + @LangMessage + public TranslatedMessage lang_title; + + @ConfigMaterial(def = Material.ENDER_PEARL, desc = "The item used to name portals.") + public Material config_material; + + public EnterNameMenu(Context context) { + super(context.namespace("enter_name")); + } + + public Menu create(final Player player, final Function2 on_click) { + return create(player, "Name", on_click); + } + + public Menu create( + final Player player, + final String default_name, + final Function2 on_click + ) { + return MenuFactory.anvil_string_input( + get_context(), + player, + lang_title.str(), + new ItemStack(config_material), + default_name, + (p, menu, name) -> { + menu.close(p); + return on_click.apply(p, name); + } + ); + } + + @Override + public void on_enable() {} + + @Override + public void on_disable() {} } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuGroup.java b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuGroup.java index 89103e131..7c671b373 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuGroup.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuGroup.java @@ -6,22 +6,22 @@ public class PortalMenuGroup extends ModuleComponent { - public EnterNameMenu enter_name_menu; - public ConsoleMenu console_menu; - public SettingsMenu settings_menu; - public StyleMenu style_menu; + public EnterNameMenu enter_name_menu; + public ConsoleMenu console_menu; + public SettingsMenu settings_menu; + public StyleMenu style_menu; - public PortalMenuGroup(Context context) { - super(context.namespace("menus")); - enter_name_menu = new EnterNameMenu(get_context()); - console_menu = new ConsoleMenu(get_context()); - settings_menu = new SettingsMenu(get_context()); - style_menu = new StyleMenu(get_context()); - } + public PortalMenuGroup(Context context) { + super(context.namespace("menus")); + enter_name_menu = new EnterNameMenu(get_context()); + console_menu = new ConsoleMenu(get_context()); + settings_menu = new SettingsMenu(get_context()); + style_menu = new StyleMenu(get_context()); + } - @Override - public void on_enable() {} + @Override + public void on_enable() {} - @Override - public void on_disable() {} + @Override + public void on_disable() {} } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuTag.java b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuTag.java index e94ace774..10a6c74b0 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuTag.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/PortalMenuTag.java @@ -4,13 +4,13 @@ public class PortalMenuTag { - private final UUID portal_id; + private final UUID portal_id; - public PortalMenuTag(final UUID portal_id) { - this.portal_id = portal_id; - } + public PortalMenuTag(final UUID portal_id) { + this.portal_id = portal_id; + } - public UUID portal_id() { - return portal_id; - } + public UUID portal_id() { + return portal_id; + } } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/SettingsMenu.java b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/SettingsMenu.java index 8ada00786..91c61f1d9 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/SettingsMenu.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/SettingsMenu.java @@ -23,355 +23,311 @@ public class SettingsMenu extends ModuleComponent { - @LangMessage - public TranslatedMessage lang_title; + @LangMessage + public TranslatedMessage lang_title; - @LangMessage - public TranslatedMessage lang_select_icon_title; + @LangMessage + public TranslatedMessage lang_select_icon_title; - public TranslatedItemStack item_rename; - public TranslatedItemStack item_select_icon; - public TranslatedItemStack item_select_style; - public TranslatedItemStack item_exit_orientation_lock_on; - public TranslatedItemStack item_exit_orientation_lock_off; - public TranslatedItemStack item_visibility_public; - public TranslatedItemStack item_visibility_group; - public TranslatedItemStack item_visibility_group_internal; - public TranslatedItemStack item_visibility_private; - public TranslatedItemStack item_target_lock_on; - public TranslatedItemStack item_target_lock_off; - public TranslatedItemStack item_back; + public TranslatedItemStack item_rename; + public TranslatedItemStack item_select_icon; + public TranslatedItemStack item_select_style; + public TranslatedItemStack item_exit_orientation_lock_on; + public TranslatedItemStack item_exit_orientation_lock_off; + public TranslatedItemStack item_visibility_public; + public TranslatedItemStack item_visibility_group; + public TranslatedItemStack item_visibility_group_internal; + public TranslatedItemStack item_visibility_private; + public TranslatedItemStack item_target_lock_on; + public TranslatedItemStack item_target_lock_off; + public TranslatedItemStack item_back; - public SettingsMenu(Context context) { - super(context.namespace("settings")); - final var ctx = get_context(); - item_rename = new TranslatedItemStack<>(ctx, "rename", Material.NAME_TAG, 1, "Used to rename the portal."); - item_select_icon = - new TranslatedItemStack<>( - ctx, - "select_icon", - StorageUtil.namespaced_key("vane", "decoration_end_portal_orb"), - 1, - "Used to select the portal's icon." - ); - item_select_style = - new TranslatedItemStack<>( - ctx, - "select_style", - Material.ITEM_FRAME, - 1, - "Used to change the portal's style." - ); - item_exit_orientation_lock_on = - new TranslatedItemStack<>( - ctx, - "exit_orientation_lock_on", - Material.SOUL_TORCH, - 1, - "Used to toggle and indicate enabled exit orientation lock." - ); - item_exit_orientation_lock_off = - new TranslatedItemStack<>( - ctx, - "exit_orientation_lock_off", - Material.TORCH, - 1, - "Used to toggle and indicate disabled exit orientation lock." - ); - item_visibility_public = - new TranslatedItemStack<>( - ctx, - "visibility_public", - Material.ENDER_EYE, - 1, - "Used to change and indicate public visibility." - ); - item_visibility_group = - new TranslatedItemStack<>( - ctx, - "visibility_group", - Material.ENDER_PEARL, - 1, - "Used to change and indicate group visibility." - ); - item_visibility_group_internal = - new TranslatedItemStack<>( - ctx, - "visibility_group_internal", - Material.FIRE_CHARGE, - 1, - "Used to change and indicate group internal visibility." - ); - item_visibility_private = - new TranslatedItemStack<>( - ctx, - "visibility_private", - Material.FIREWORK_STAR, - 1, - "Used to change and indicate private visibility." - ); - item_target_lock_on = - new TranslatedItemStack<>( - ctx, - "target_lock_on", - Material.SLIME_BALL, - 1, - "Used to toggle and indicate enabled target lock." - ); - item_target_lock_off = - new TranslatedItemStack<>( - ctx, - "target_lock_off", - Material.SNOWBALL, - 1, - "Used to toggle and indicate disabled target lock." - ); - item_back = - new TranslatedItemStack<>( - ctx, - "back", - Material.PRISMARINE_SHARD, - 1, - "Used to go back to the previous menu." - ); - } + public SettingsMenu(Context context) { + super(context.namespace("settings")); + final var ctx = get_context(); + item_rename = new TranslatedItemStack<>(ctx, "rename", Material.NAME_TAG, 1, "Used to rename the portal."); + item_select_icon = new TranslatedItemStack<>( + ctx, + "select_icon", + StorageUtil.namespaced_key("vane", "decoration_end_portal_orb"), + 1, + "Used to select the portal's icon." + ); + item_select_style = new TranslatedItemStack<>( + ctx, + "select_style", + Material.ITEM_FRAME, + 1, + "Used to change the portal's style." + ); + item_exit_orientation_lock_on = new TranslatedItemStack<>( + ctx, + "exit_orientation_lock_on", + Material.SOUL_TORCH, + 1, + "Used to toggle and indicate enabled exit orientation lock." + ); + item_exit_orientation_lock_off = new TranslatedItemStack<>( + ctx, + "exit_orientation_lock_off", + Material.TORCH, + 1, + "Used to toggle and indicate disabled exit orientation lock." + ); + item_visibility_public = new TranslatedItemStack<>( + ctx, + "visibility_public", + Material.ENDER_EYE, + 1, + "Used to change and indicate public visibility." + ); + item_visibility_group = new TranslatedItemStack<>( + ctx, + "visibility_group", + Material.ENDER_PEARL, + 1, + "Used to change and indicate group visibility." + ); + item_visibility_group_internal = new TranslatedItemStack<>( + ctx, + "visibility_group_internal", + Material.FIRE_CHARGE, + 1, + "Used to change and indicate group internal visibility." + ); + item_visibility_private = new TranslatedItemStack<>( + ctx, + "visibility_private", + Material.FIREWORK_STAR, + 1, + "Used to change and indicate private visibility." + ); + item_target_lock_on = new TranslatedItemStack<>( + ctx, + "target_lock_on", + Material.SLIME_BALL, + 1, + "Used to toggle and indicate enabled target lock." + ); + item_target_lock_off = new TranslatedItemStack<>( + ctx, + "target_lock_off", + Material.SNOWBALL, + 1, + "Used to toggle and indicate disabled target lock." + ); + item_back = new TranslatedItemStack<>( + ctx, + "back", + Material.PRISMARINE_SHARD, + 1, + "Used to go back to the previous menu." + ); + } - // HINT: We don't capture the previous menu and open a new one on exit, - // to correctly reflect changes done in here. (e.g., menu title due to portal name) - public Menu create(final Portal portal, final Player player, final Block console) { - final var columns = 9; - final var title = lang_title.str_component("§5§l" + portal.name()); - final var settings_menu = new Menu(get_context(), Bukkit.createInventory(null, columns, title)); - settings_menu.tag(new PortalMenuTag(portal.id())); + // HINT: We don't capture the previous menu and open a new one on exit, + // to correctly reflect changes done in here. (e.g., menu title due to portal name) + public Menu create(final Portal portal, final Player player, final Block console) { + final var columns = 9; + final var title = lang_title.str_component("§5§l" + portal.name()); + final var settings_menu = new Menu(get_context(), Bukkit.createInventory(null, columns, title)); + settings_menu.tag(new PortalMenuTag(portal.id())); - settings_menu.add(menu_item_rename(portal, console)); - settings_menu.add(menu_item_select_icon(portal)); - settings_menu.add(menu_item_select_style(portal)); - settings_menu.add(menu_item_exit_orientation_lock(portal)); - settings_menu.add(menu_item_visibility(portal)); - settings_menu.add(menu_item_target_lock(portal)); - settings_menu.add(menu_item_back(portal, console)); + settings_menu.add(menu_item_rename(portal, console)); + settings_menu.add(menu_item_select_icon(portal)); + settings_menu.add(menu_item_select_style(portal)); + settings_menu.add(menu_item_exit_orientation_lock(portal)); + settings_menu.add(menu_item_visibility(portal)); + settings_menu.add(menu_item_target_lock(portal)); + settings_menu.add(menu_item_back(portal, console)); - settings_menu.on_natural_close(player2 -> - get_module().menus.console_menu.create(portal, player2, console).open(player2) - ); + settings_menu.on_natural_close(player2 -> + get_module().menus.console_menu.create(portal, player2, console).open(player2) + ); - return settings_menu; - } + return settings_menu; + } - private MenuWidget menu_item_rename(final Portal portal, final Block console) { - return new MenuItem( - 0, - item_rename.item(), - (player, menu, self) -> { - menu.close(player); + private MenuWidget menu_item_rename(final Portal portal, final Block console) { + return new MenuItem(0, item_rename.item(), (player, menu, self) -> { + menu.close(player); - get_module() - .menus.enter_name_menu.create( - player, - portal.name(), - (player2, name) -> { - final var settings_event = new PortalChangeSettingsEvent(player2, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player2); - return ClickResult.ERROR; - } + get_module() + .menus.enter_name_menu.create(player, portal.name(), (player2, name) -> { + final var settings_event = new PortalChangeSettingsEvent(player2, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player2); + return ClickResult.ERROR; + } - portal.name(name); + portal.name(name); - // Update portal icons to reflect new name - get_module().update_portal_icon(portal); + // Update portal icons to reflect new name + get_module().update_portal_icon(portal); - // Open new menu because of possibly changed title - get_module().menus.settings_menu.create(portal, player2, console).open(player2); - return ClickResult.SUCCESS; - } - ) - .on_natural_close(player2 -> { - // Open new menu because of possibly changed title - get_module().menus.settings_menu.create(portal, player2, console).open(player2); - }) - .open(player); + // Open new menu because of possibly changed title + get_module().menus.settings_menu.create(portal, player2, console).open(player2); + return ClickResult.SUCCESS; + }) + .on_natural_close(player2 -> { + // Open new menu because of possibly changed title + get_module().menus.settings_menu.create(portal, player2, console).open(player2); + }) + .open(player); - return ClickResult.SUCCESS; - } - ); - } + return ClickResult.SUCCESS; + }); + } - private MenuWidget menu_item_select_icon(final Portal portal) { - return new MenuItem( - 1, - item_select_icon.item(), - (player, menu, self) -> { - menu.close(player); - MenuFactory - .item_selector( - get_context(), - player, - lang_select_icon_title.str(), - portal.icon(), - true, - (player2, item) -> { - final var settings_event = new PortalChangeSettingsEvent(player2, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player2); - return ClickResult.ERROR; - } + private MenuWidget menu_item_select_icon(final Portal portal) { + return new MenuItem(1, item_select_icon.item(), (player, menu, self) -> { + menu.close(player); + MenuFactory.item_selector( + get_context(), + player, + lang_select_icon_title.str(), + portal.icon(), + true, + (player2, item) -> { + final var settings_event = new PortalChangeSettingsEvent(player2, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player2.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player2); + return ClickResult.ERROR; + } - portal.icon(item); - get_module().update_portal_icon(portal); - menu.open(player2); - return ClickResult.SUCCESS; - }, - player2 -> menu.open(player2) - ) - .tag(new PortalMenuTag(portal.id())) - .open(player); - return ClickResult.SUCCESS; - } - ); - } + portal.icon(item); + get_module().update_portal_icon(portal); + menu.open(player2); + return ClickResult.SUCCESS; + }, + player2 -> menu.open(player2) + ) + .tag(new PortalMenuTag(portal.id())) + .open(player); + return ClickResult.SUCCESS; + }); + } - private MenuWidget menu_item_select_style(final Portal portal) { - return new MenuItem( - 2, - item_select_style.item(), - (player, menu, self) -> { - final var settings_event = new PortalChangeSettingsEvent(player, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player); - return ClickResult.ERROR; - } + private MenuWidget menu_item_select_style(final Portal portal) { + return new MenuItem(2, item_select_style.item(), (player, menu, self) -> { + final var settings_event = new PortalChangeSettingsEvent(player, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player); + return ClickResult.ERROR; + } - menu.close(player); - get_module().menus.style_menu.create(portal, player, menu).open(player); - return ClickResult.SUCCESS; - } - ); - } + menu.close(player); + get_module().menus.style_menu.create(portal, player, menu).open(player); + return ClickResult.SUCCESS; + }); + } - private MenuWidget menu_item_exit_orientation_lock(final Portal portal) { - return new MenuItem( - 4, - null, - (player, menu, self) -> { - final var settings_event = new PortalChangeSettingsEvent(player, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player); - return ClickResult.ERROR; - } + private MenuWidget menu_item_exit_orientation_lock(final Portal portal) { + return new MenuItem(4, null, (player, menu, self) -> { + final var settings_event = new PortalChangeSettingsEvent(player, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player); + return ClickResult.ERROR; + } - portal.exit_orientation_locked(!portal.exit_orientation_locked()); - menu.update(); - return ClickResult.SUCCESS; - } - ) { - @Override - public void item(final ItemStack item) { - if (portal.exit_orientation_locked()) { - super.item(item_exit_orientation_lock_on.item()); - } else { - super.item(item_exit_orientation_lock_off.item()); - } - } - }; - } + portal.exit_orientation_locked(!portal.exit_orientation_locked()); + menu.update(); + return ClickResult.SUCCESS; + }) { + @Override + public void item(final ItemStack item) { + if (portal.exit_orientation_locked()) { + super.item(item_exit_orientation_lock_on.item()); + } else { + super.item(item_exit_orientation_lock_off.item()); + } + } + }; + } - private MenuWidget menu_item_visibility(final Portal portal) { - return new MenuItem( - 5, - null, - (player, menu, self, event) -> { - if (!Menu.is_left_or_right_click(event)) { - return ClickResult.INVALID_CLICK; - } + private MenuWidget menu_item_visibility(final Portal portal) { + return new MenuItem(5, null, (player, menu, self, event) -> { + if (!Menu.is_left_or_right_click(event)) { + return ClickResult.INVALID_CLICK; + } - final var settings_event = new PortalChangeSettingsEvent(player, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player); - return ClickResult.ERROR; - } + final var settings_event = new PortalChangeSettingsEvent(player, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player); + return ClickResult.ERROR; + } - Portal.Visibility new_vis = portal.visibility(); - // If the "regions" plugin is not installed, we need to skip group visibility. - do { - new_vis = event.getClick() == ClickType.RIGHT ? new_vis.prev() : new_vis.next(); - } while (new_vis.requires_regions() && !get_module().is_regions_installed()); + Portal.Visibility new_vis = portal.visibility(); + // If the "regions" plugin is not installed, we need to skip group visibility. + do { + new_vis = event.getClick() == ClickType.RIGHT ? new_vis.prev() : new_vis.next(); + } while (new_vis.requires_regions() && !get_module().is_regions_installed()); - portal.visibility(new_vis); - get_module().update_portal_visibility(portal); - menu.update(); - return ClickResult.SUCCESS; - } - ) { - @Override - public void item(final ItemStack item) { - switch (portal.visibility()) { - case PUBLIC: - super.item(item_visibility_public.item()); - break; - case GROUP: - super.item(item_visibility_group.item()); - break; - case GROUP_INTERNAL: - super.item(item_visibility_group_internal.item()); - break; - case PRIVATE: - super.item(item_visibility_private.item()); - break; - } - } - }; - } + portal.visibility(new_vis); + get_module().update_portal_visibility(portal); + menu.update(); + return ClickResult.SUCCESS; + }) { + @Override + public void item(final ItemStack item) { + switch (portal.visibility()) { + case PUBLIC: + super.item(item_visibility_public.item()); + break; + case GROUP: + super.item(item_visibility_group.item()); + break; + case GROUP_INTERNAL: + super.item(item_visibility_group_internal.item()); + break; + case PRIVATE: + super.item(item_visibility_private.item()); + break; + } + } + }; + } - private MenuWidget menu_item_target_lock(final Portal portal) { - return new MenuItem( - 6, - null, - (player, menu, self) -> { - final var settings_event = new PortalChangeSettingsEvent(player, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - get_module().lang_settings_restricted.send(player); - return ClickResult.ERROR; - } + private MenuWidget menu_item_target_lock(final Portal portal) { + return new MenuItem(6, null, (player, menu, self) -> { + final var settings_event = new PortalChangeSettingsEvent(player, portal, false); + get_module().getServer().getPluginManager().callEvent(settings_event); + if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { + get_module().lang_settings_restricted.send(player); + return ClickResult.ERROR; + } - portal.target_locked(!portal.target_locked()); - menu.update(); - return ClickResult.SUCCESS; - } - ) { - @Override - public void item(final ItemStack item) { - if (portal.target_locked()) { - super.item(item_target_lock_on.item()); - } else { - super.item(item_target_lock_off.item()); - } - } - }; - } + portal.target_locked(!portal.target_locked()); + menu.update(); + return ClickResult.SUCCESS; + }) { + @Override + public void item(final ItemStack item) { + if (portal.target_locked()) { + super.item(item_target_lock_on.item()); + } else { + super.item(item_target_lock_off.item()); + } + } + }; + } - private MenuWidget menu_item_back(final Portal portal, final Block console) { - return new MenuItem( - 8, - item_back.item(), - (player, menu, self) -> { - menu.close(player); - get_module().menus.console_menu.create(portal, player, console).open(player); - return ClickResult.SUCCESS; - } - ); - } + private MenuWidget menu_item_back(final Portal portal, final Block console) { + return new MenuItem(8, item_back.item(), (player, menu, self) -> { + menu.close(player); + get_module().menus.console_menu.create(portal, player, console).open(player); + return ClickResult.SUCCESS; + }); + } - @Override - public void on_enable() {} + @Override + public void on_enable() {} - @Override - public void on_disable() {} + @Override + public void on_disable() {} } diff --git a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/StyleMenu.java b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/StyleMenu.java index 1093a0421..8228026c6 100644 --- a/vane-portals/src/main/java/org/oddlama/vane/portals/menu/StyleMenu.java +++ b/vane-portals/src/main/java/org/oddlama/vane/portals/menu/StyleMenu.java @@ -26,569 +26,610 @@ public class StyleMenu extends ModuleComponent { - private static final int columns = 9; - - @LangMessage - public TranslatedMessage lang_title; - - @LangMessage - public TranslatedMessage lang_select_block_console_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_origin_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_portal_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_1_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_2_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_3_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_4_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_5_active_title; - - @LangMessage - public TranslatedMessage lang_select_block_console_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_origin_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_portal_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_1_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_2_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_3_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_4_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_block_boundary_5_inactive_title; - - @LangMessage - public TranslatedMessage lang_select_style_title; - - @LangMessage - public TranslatedMessage lang_filter_styles_title; - - private TranslatedItemStack item_block_console_active; - private TranslatedItemStack item_block_origin_active; - private TranslatedItemStack item_block_portal_active; - private TranslatedItemStack item_block_boundary_1_active; - private TranslatedItemStack item_block_boundary_2_active; - private TranslatedItemStack item_block_boundary_3_active; - private TranslatedItemStack item_block_boundary_4_active; - private TranslatedItemStack item_block_boundary_5_active; - private TranslatedItemStack item_block_console_inactive; - private TranslatedItemStack item_block_origin_inactive; - private TranslatedItemStack item_block_portal_inactive; - private TranslatedItemStack item_block_boundary_1_inactive; - private TranslatedItemStack item_block_boundary_2_inactive; - private TranslatedItemStack item_block_boundary_3_inactive; - private TranslatedItemStack item_block_boundary_4_inactive; - private TranslatedItemStack item_block_boundary_5_inactive; - - private TranslatedItemStack item_accept; - private TranslatedItemStack item_reset; - private TranslatedItemStack item_select_defined; - private TranslatedItemStack item_select_style; - private TranslatedItemStack item_cancel; - - public StyleMenu(Context context) { - super(context.namespace("style")); - final var ctx = get_context(); - item_block_console_active = new TranslatedItemStack<>( - ctx, - "block_console_active", - Material.BARRIER, - 1, - "Used to select active console block."); - item_block_origin_active = new TranslatedItemStack<>( - ctx, - "block_origin_active", - Material.BARRIER, - 1, - "Used to select active origin block."); - item_block_portal_active = new TranslatedItemStack<>( - ctx, - "block_portal_active", - Material.BARRIER, - 1, - "Used to select active portal area block. Defaults to end gateway if unset."); - item_block_boundary_1_active = new TranslatedItemStack<>( - ctx, - "block_boundary_1_active", - Material.BARRIER, - 1, - "Used to select active boundary variant 1 block."); - item_block_boundary_2_active = new TranslatedItemStack<>( - ctx, - "block_boundary_2_active", - Material.BARRIER, - 1, - "Used to select active boundary variant 2 block."); - item_block_boundary_3_active = new TranslatedItemStack<>( - ctx, - "block_boundary_3_active", - Material.BARRIER, - 1, - "Used to select active boundary variant 3 block."); - item_block_boundary_4_active = new TranslatedItemStack<>( - ctx, - "block_boundary_4_active", - Material.BARRIER, - 1, - "Used to select active boundary variant 4 block."); - item_block_boundary_5_active = new TranslatedItemStack<>( - ctx, - "block_boundary_5_active", - Material.BARRIER, - 1, - "Used to select active boundary variant 5 block."); - item_block_console_inactive = new TranslatedItemStack<>( - ctx, - "block_console_inactive", - Material.BARRIER, - 1, - "Used to select inactive console block."); - item_block_origin_inactive = new TranslatedItemStack<>( - ctx, - "block_origin_inactive", - Material.BARRIER, - 1, - "Used to select inactive origin block."); - item_block_portal_inactive = new TranslatedItemStack<>( - ctx, - "block_portal_inactive", - Material.BARRIER, - 1, - "Used to select inactive portal area block."); - item_block_boundary_1_inactive = new TranslatedItemStack<>( - ctx, - "block_boundary_1_inactive", - Material.BARRIER, - 1, - "Used to select inactive boundary variant 1 block."); - item_block_boundary_2_inactive = new TranslatedItemStack<>( - ctx, - "block_boundary_2_inactive", - Material.BARRIER, - 1, - "Used to select inactive boundary variant 2 block."); - item_block_boundary_3_inactive = new TranslatedItemStack<>( - ctx, - "block_boundary_3_inactive", - Material.BARRIER, - 1, - "Used to select inactive boundary variant 3 block."); - item_block_boundary_4_inactive = new TranslatedItemStack<>( - ctx, - "block_boundary_4_inactive", - Material.BARRIER, - 1, - "Used to select inactive boundary variant 4 block."); - item_block_boundary_5_inactive = new TranslatedItemStack<>( - ctx, - "block_boundary_5_inactive", - Material.BARRIER, - 1, - "Used to select inactive boundary variant 5 block."); - - item_accept = new TranslatedItemStack<>(ctx, "accept", Material.LIME_TERRACOTTA, 1, "Used to apply the style."); - item_reset = new TranslatedItemStack<>(ctx, "reset", Material.MILK_BUCKET, 1, "Used to reset any changes."); - item_select_defined = new TranslatedItemStack<>( - ctx, - "select_defined", - Material.ITEM_FRAME, - 1, - "Used to select a defined style from the configuration."); - item_select_style = new TranslatedItemStack<>( - ctx, - "select_style", - Material.ITEM_FRAME, - 1, - "Used to represent a defined style in the selector menu."); - item_cancel = new TranslatedItemStack<>(ctx, "cancel", Material.RED_TERRACOTTA, 1, - "Used to abort style selection."); - } - - public Menu create(final Portal portal, final Player player, final Menu previous) { - final var title = lang_title.str_component("§5§l" + portal.name()); - final var style_menu = new Menu(get_context(), Bukkit.createInventory(null, 4 * columns, title)); - style_menu.tag(new PortalMenuTag(portal.id())); - - final var style_container = new StyleContainer(); - style_container.defined_style = portal.style(); - style_container.style = portal.copy_style(get_module(), null); - - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 0, - item_block_console_inactive, - get_module().constructor.config_material_console, - lang_select_block_console_inactive_title.str(), - PortalBlock.Type.CONSOLE, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 1, - item_block_origin_inactive, - get_module().constructor.config_material_origin, - lang_select_block_origin_inactive_title.str(), - PortalBlock.Type.ORIGIN, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 2, - item_block_portal_inactive, - get_module().constructor.config_material_portal_area, - lang_select_block_portal_inactive_title.str(), - PortalBlock.Type.PORTAL, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 4, - item_block_boundary_1_inactive, - get_module().constructor.config_material_boundary_1, - lang_select_block_boundary_1_inactive_title.str(), - PortalBlock.Type.BOUNDARY_1, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 5, - item_block_boundary_2_inactive, - get_module().constructor.config_material_boundary_2, - lang_select_block_boundary_2_inactive_title.str(), - PortalBlock.Type.BOUNDARY_2, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 6, - item_block_boundary_3_inactive, - get_module().constructor.config_material_boundary_3, - lang_select_block_boundary_3_inactive_title.str(), - PortalBlock.Type.BOUNDARY_3, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 7, - item_block_boundary_4_inactive, - get_module().constructor.config_material_boundary_4, - lang_select_block_boundary_4_inactive_title.str(), - PortalBlock.Type.BOUNDARY_4, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - 8, - item_block_boundary_5_inactive, - get_module().constructor.config_material_boundary_5, - lang_select_block_boundary_5_inactive_title.str(), - PortalBlock.Type.BOUNDARY_5, - false)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns, - item_block_console_active, - get_module().constructor.config_material_console, - lang_select_block_console_active_title.str(), - PortalBlock.Type.CONSOLE, - true)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns + 1, - item_block_origin_active, - get_module().constructor.config_material_origin, - lang_select_block_origin_active_title.str(), - PortalBlock.Type.ORIGIN, - true)); - // style_menu.add(menu_item_block_selector(portal, style_container, 1 * columns - // + 2, item_block_portal_active, - // get_module().constructor.config_material_portal_area, - // lang_select_block_portal_active_title.str(), PortalBlock.Type.PORTAL, true)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns + 4, - item_block_boundary_1_active, - get_module().constructor.config_material_boundary_1, - lang_select_block_boundary_1_active_title.str(), - PortalBlock.Type.BOUNDARY_1, - true)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns + 5, - item_block_boundary_2_active, - get_module().constructor.config_material_boundary_2, - lang_select_block_boundary_2_active_title.str(), - PortalBlock.Type.BOUNDARY_2, - true)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns + 6, - item_block_boundary_3_active, - get_module().constructor.config_material_boundary_3, - lang_select_block_boundary_3_active_title.str(), - PortalBlock.Type.BOUNDARY_3, - true)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns + 7, - item_block_boundary_4_active, - get_module().constructor.config_material_boundary_4, - lang_select_block_boundary_4_active_title.str(), - PortalBlock.Type.BOUNDARY_4, - true)); - style_menu.add( - menu_item_block_selector( - portal, - style_container, - columns + 8, - item_block_boundary_5_active, - get_module().constructor.config_material_boundary_5, - lang_select_block_boundary_5_active_title.str(), - PortalBlock.Type.BOUNDARY_5, - true)); - - style_menu.add(menu_item_accept(portal, style_container, previous)); - style_menu.add(menu_item_reset(portal, style_container)); - style_menu.add(menu_item_select_defined(portal, style_container)); - style_menu.add(menu_item_cancel(previous)); - - style_menu.on_natural_close(player2 -> previous.open(player2)); - return style_menu; - } - - private static ItemStack item_for_type( - final StyleContainer style_container, - final boolean active, - final PortalBlock.Type type) { - if (active && type == PortalBlock.Type.PORTAL) { - return new ItemStack(Material.AIR); - } - return new ItemStack(style_container.style.material(active, type)); - } - - private MenuWidget menu_item_block_selector( - final Portal portal, - final StyleContainer style_container, - int slot, - final TranslatedItemStack t_item, - final Material building_material, - final String title, - final PortalBlock.Type type, - final boolean active - ) { - return new MenuItem( - slot, - null, - (player, menu, self) -> { - menu.close(player); - MenuFactory - .item_selector( - get_context(), - player, - title, - item_for_type(style_container, active, type), - true, - (player2, item) -> { - style_container.defined_style = null; - if (item == null) { - if (active && type == PortalBlock.Type.PORTAL) { - style_container.style.set_material(active, type, Material.END_GATEWAY, true); - } - style_container.style.set_material(active, type, Material.AIR, true); - } else { - style_container.style.set_material(active, type, item.getType(), true); - } - menu.open(player2); - return ClickResult.SUCCESS; - }, - player2 -> menu.open(player2), - item -> { - // Only allow placeable solid blocks - if (item == null || !(item.getType().isBlock() && item.getType().isSolid())) { - return null; - } - - // Nothing from the blacklist - if (get_module().config_blacklisted_materials.contains(item.getType())) { - return null; - } - - // Must be a block (should be given at this point) - var block = item.getType().asBlockType(); - if (block == null) { - return null; - } - - // Must be a full block with one AABB of extent (1,1,1) - var blockdata = block.createBlockData(); - var voxelshape = blockdata.getCollisionShape(player.getLocation()); - var bbs = voxelshape.getBoundingBoxes(); - if (bbs.size() != 1 || !bbs.stream().allMatch(x -> x.getWidthX() == 1.0 && x.getWidthZ() == 1.0 && x.getHeight() == 1.0)) { - return null; - } - - // Always select one - item.setAmount(1); - return item; - } - ) - .tag(new PortalMenuTag(portal.id())) - .open(player); - menu.update(); - return ClickResult.SUCCESS; - } - ) { - @Override - public void item(final ItemStack item) { - var stack = item_for_type(style_container, active, type); - if (stack.getType() == Material.AIR) { - stack = stack.withType(Material.BARRIER); - } - super.item(t_item.alternative(stack, "§6" + building_material.getKey())); - } - }; - } - - private MenuWidget menu_item_accept( - final Portal portal, - final StyleContainer style_container, - final Menu previous) { - return new MenuItem( - 3 * columns, - item_accept.item(), - (player, menu, self) -> { - menu.close(player); - - final var settings_event = new PortalChangeSettingsEvent(player, portal, false); - get_module().getServer().getPluginManager().callEvent(settings_event); - if (settings_event.isCancelled() && !player.hasPermission(get_module().admin_permission)) { - return ClickResult.ERROR; - } - - portal.style(style_container.style); - portal.update_blocks(get_module()); - previous.open(player); - return ClickResult.SUCCESS; - }); - } - - private MenuWidget menu_item_reset(final Portal portal, final StyleContainer style_container) { - return new MenuItem( - 3 * columns + 3, - item_reset.item(), - (player, menu, self) -> { - style_container.style = portal.copy_style(get_module(), null); - menu.update(); - return ClickResult.SUCCESS; - }); - } - - private MenuWidget menu_item_select_defined(final Portal portal, final StyleContainer style_container) { - final Function1 item_for = style -> { - final var mat = style.material(false, PortalBlock.Type.BOUNDARY_1); - if (mat == null) { - return new ItemStack(Material.BARRIER); - } else { - return new ItemStack(mat); - } - }; - - return new MenuItem( - 3 * columns + 4, - item_select_defined.item(), - (player, menu, self) -> { - menu.close(player); - final var all_styles = new ArrayList<>(get_module().styles.values()); - final var filter = new Filter.StringFilter