diff --git a/docs/modules/Timer.md b/docs/modules/Timer.md index 9cb5926..5c0b61c 100644 --- a/docs/modules/Timer.md +++ b/docs/modules/Timer.md @@ -7,11 +7,16 @@ The icon for this module is clock. For documentation on the timer command visit the [/timer documentation](../commands/timer.md) -At least 1 of 'boss bar' or 'action bar' must be used for this module to load. -If neither are enabled this module will fail to load, if both are loaded then -the boss bar takes precendence. +At least 1 of 'boss bar' or 'action bar' or 'tab list' must be used for this module to load. +If none are enabled this module will fail to load, if all are loaded then +they are use in this order: + +BOSS BAR > TAB LIST > ACTION BAR + +BOSS BAR requires Minecraft 1.9+ + +TAB LIST and ACTION BAR both require ProtocolLib to also be installed -This module requires either 1.9+ (boss bar) or ProtocolLib (action bar) to run ### Configuration @@ -19,6 +24,8 @@ This module requires either 1.9+ (boss bar) or ProtocolLib (action bar) to run use boss bar: true boss bar colour: BLUE boss bar style: SOLID +use tab list: true +tab list position: BOTTOM use action bar: true ``` @@ -28,6 +35,10 @@ use action bar: true `boss bar style` The style of the boss bar, values can be found [here](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarStyle.html) +`use tab list` Whether to use the tab list for the timer, requires ProtocolLib + +`tab list position` Either TOP or BOTTOM, what end of the tab list to render on + `use action bar` Whether to use the action bar for the timer, requires ProtocolLib diff --git a/src/main/java/gg/uhc/uhc/modules/timer/TimerModule.java b/src/main/java/gg/uhc/uhc/modules/timer/TimerModule.java index b5b87cd..19d3390 100644 --- a/src/main/java/gg/uhc/uhc/modules/timer/TimerModule.java +++ b/src/main/java/gg/uhc/uhc/modules/timer/TimerModule.java @@ -32,24 +32,25 @@ import gg.uhc.uhc.modules.Module; import gg.uhc.uhc.modules.ModuleRegistry; import gg.uhc.uhc.modules.timer.messages.TimerMessage; -import gg.uhc.uhc.modules.timer.renderer.ActionBarRenderer; -import gg.uhc.uhc.modules.timer.renderer.BossBarRenderer; -import gg.uhc.uhc.modules.timer.renderer.TimerRenderer; +import gg.uhc.uhc.modules.timer.renderer.*; import gg.uhc.uhc.util.ActionBarMessenger; import com.comphenix.protocol.ProtocolLibrary; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.boss.BarColor; import org.bukkit.boss.BarStyle; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; -import java.util.List; +import java.util.Iterator; public class TimerModule extends Module { @@ -57,6 +58,9 @@ public class TimerModule extends Module { protected static final String BOSS_BAR_COLOUR_KEY = "boss bar colour"; protected static final String BOSS_BAR_STYLE_KEY = "boss bar style"; protected static final String USE_ACTION_BAR_KEY = "use action bar"; + protected static final String USE_TAB_LIST_KEY = "use tab list"; + protected static final String TAB_LIST_POSITION_KEY = "tab list position"; + protected static final int TICKS_PER_SECOND = 20; protected static final double PERCENT_MULTIPLIER = 100D; @@ -74,88 +78,140 @@ public TimerModule() { this.icon.setWeight(ModuleRegistry.CATEGORY_MISC); } - @Override - public void initialize() throws InvalidConfigurationException { - this.icon.setLore(messages.getRawStrings("lore")); - - final List renderers = Lists.newArrayList(); - + protected TimerRenderer setupBossBar(ConfigurationSection section) { if (!config.contains(USE_BOSS_BAR_KEY)) { config.set(USE_BOSS_BAR_KEY, true); } - if (config.getBoolean(USE_BOSS_BAR_KEY)) { - try { - if (!config.contains(BOSS_BAR_COLOUR_KEY)) { - config.set(BOSS_BAR_COLOUR_KEY, BarColor.BLUE.name()); - } - if (!config.contains(BOSS_BAR_STYLE_KEY)) { - config.set(BOSS_BAR_STYLE_KEY, BarStyle.SOLID.name()); - } + if (!config.getBoolean(USE_BOSS_BAR_KEY)) { + return null; + } - // attempt to parse the colour - BarColor colour; - try { - colour = EnumConverter.forEnum(BarColor.class).convert(config.getString(BOSS_BAR_COLOUR_KEY)); - } catch (ValueConversionException ex) { - plugin.getLogger().warning("Invalid colour for boss bar, switching to blue"); - config.set(BOSS_BAR_COLOUR_KEY, BarColor.BLUE.name()); - colour = BarColor.BLUE; - } + try { + if (!config.contains(BOSS_BAR_COLOUR_KEY)) { + config.set(BOSS_BAR_COLOUR_KEY, BarColor.BLUE.name()); + } + if (!config.contains(BOSS_BAR_STYLE_KEY)) { + config.set(BOSS_BAR_STYLE_KEY, BarStyle.SOLID.name()); + } - // attempt to parse the style - BarStyle style; - try { - style = EnumConverter.forEnum(BarStyle.class).convert(config.getString(BOSS_BAR_STYLE_KEY)); - } catch (ValueConversionException ex) { - plugin.getLogger().warning("Invalid style for boss bar, switching to solid"); - config.set(BOSS_BAR_STYLE_KEY, BarStyle.SOLID.name()); - style = BarStyle.SOLID; - } + // attempt to parse the colour + BarColor colour; + try { + colour = EnumConverter.forEnum(BarColor.class).convert(config.getString(BOSS_BAR_COLOUR_KEY)); + } catch (ValueConversionException ex) { + plugin.getLogger().warning("Invalid colour for boss bar, switching to blue"); + config.set(BOSS_BAR_COLOUR_KEY, BarColor.BLUE.name()); + colour = BarColor.BLUE; + } - // setup the renderer - final BossBarRenderer bossbar = new BossBarRenderer(Bukkit.createBossBar("", colour, style)); - Bukkit.getPluginManager().registerEvents(bossbar, plugin); - renderers.add(bossbar); - } catch (NoClassDefFoundError ex) { - // happens when boss bar API not implemented, < 1.9 - plugin.getLogger().severe( - "Could not load the boss bar timer type, this is only supported in 1.9+, " - + "disabling in the config file..." - ); - - // turn of the boss bar in the config to stop it happening over again - config.set(USE_BOSS_BAR_KEY, false); + // attempt to parse the style + BarStyle style; + try { + style = EnumConverter.forEnum(BarStyle.class).convert(config.getString(BOSS_BAR_STYLE_KEY)); + } catch (ValueConversionException ex) { + plugin.getLogger().warning("Invalid style for boss bar, switching to solid"); + config.set(BOSS_BAR_STYLE_KEY, BarStyle.SOLID.name()); + style = BarStyle.SOLID; } + + // setup the renderer + return new BossBarRenderer(Bukkit.createBossBar("", colour, style)); + } catch (NoClassDefFoundError ex) { + // happens when boss bar API not implemented, < 1.9 + plugin.getLogger().severe( + "Could not load the boss bar timer type, this is only supported in 1.9+, " + + "disabling in the config file..." + ); + + // turn of the boss bar in the config to stop it happening over again + config.set(USE_BOSS_BAR_KEY, false); + return null; } + } + protected TimerRenderer setupActionBar(ConfigurationSection section) { if (!config.contains(USE_ACTION_BAR_KEY)) { config.set(USE_ACTION_BAR_KEY, true); } - if (config.getBoolean(USE_ACTION_BAR_KEY)) { - try { - renderers.add(new ActionBarRenderer(new ActionBarMessenger(ProtocolLibrary.getProtocolManager()))); - } catch (NoClassDefFoundError ex) { - // Happens when protocollib isn't installed, don't disable in config just give a warning - plugin.getLogger().severe( - "Could not load the action bar timer type," - + "this is only supported when ProtocolLib is installed." - ); - } + + if (!config.getBoolean(USE_ACTION_BAR_KEY)) { + return null; } + + try { + return new ActionBarRenderer(new ActionBarMessenger(ProtocolLibrary.getProtocolManager())); + } catch (NoClassDefFoundError ex) { + // Happens when protocollib isn't installed, don't disable in config just give a warning + plugin.getLogger().severe( + "Could not load the action bar timer type," + + "this is only supported when ProtocolLib is installed." + ); + return null; + } + } + + protected TimerRenderer setupTabList(ConfigurationSection section) { + if (!config.contains(USE_TAB_LIST_KEY)) { + config.set(USE_TAB_LIST_KEY, true); + } + + if (!config.getBoolean(USE_TAB_LIST_KEY)) { + return null; + } + + if (!config.contains(TAB_LIST_POSITION_KEY)) { + config.set(TAB_LIST_POSITION_KEY, TabListPosition.BOTTOM.name()); + } + + // attempt to parse the style + TabListPosition position; + try { + position = EnumConverter.forEnum(TabListPosition.class).convert(config.getString(TAB_LIST_POSITION_KEY)); + } catch (ValueConversionException ex) { + plugin.getLogger().warning("Invalid postion for tab list, switching to BOTTOM"); + config.set(TAB_LIST_POSITION_KEY, TabListPosition.BOTTOM.name()); + position = TabListPosition.BOTTOM; + } + + try { + return new TabListRenderer(plugin, ProtocolLibrary.getProtocolManager(), position); + } catch (NoClassDefFoundError ex) { + // Happens when protocollib isn't installed, don't disable in config just give a warning + plugin.getLogger().severe( + "Could not load the tab list timer type, this is only supported when ProtocolLib is installed." + ); + return null; + } + } + + + @Override + public void initialize() throws InvalidConfigurationException { + this.icon.setLore(messages.getRawStrings("lore")); + + final Iterator renderers = Iterables.filter( + Lists.newArrayList( + setupBossBar(config), + setupTabList(config), + setupActionBar(config) + ), + Predicates.notNull() + ).iterator(); + // No renders worked, throw an error to stop the module from loading - if (renderers.size() == 0) { + if (!renderers.hasNext()) { throw new InvalidConfigurationException( - "The timer module can only be used when the Boss bar or Action bar type are loaded" + "The timer module can only be used when the Boss bar, Action bar or Tab list type are loaded" ); } // if more than one renderer is chosen pick the first one - renderer = renderers.get(0); + renderer = renderers.next(); - if (renderers.size() > 1) { + if (renderers.hasNext()) { plugin.getLogger().warning( "More than one style of timer is being used, using only the first one loaded " + "(" + renderer.getClass().getName() + ")" diff --git a/src/main/java/gg/uhc/uhc/modules/timer/renderer/TabListPosition.java b/src/main/java/gg/uhc/uhc/modules/timer/renderer/TabListPosition.java new file mode 100644 index 0000000..4dfb94c --- /dev/null +++ b/src/main/java/gg/uhc/uhc/modules/timer/renderer/TabListPosition.java @@ -0,0 +1,33 @@ +/* + * Project: UHC + * Class: gg.uhc.uhc.modules.timer.renderer.TabListPosition + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Graham Howden . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package gg.uhc.uhc.modules.timer.renderer; + +public enum TabListPosition { + TOP, + BOTTOM +} diff --git a/src/main/java/gg/uhc/uhc/modules/timer/renderer/TabListRenderer.java b/src/main/java/gg/uhc/uhc/modules/timer/renderer/TabListRenderer.java new file mode 100644 index 0000000..374c853 --- /dev/null +++ b/src/main/java/gg/uhc/uhc/modules/timer/renderer/TabListRenderer.java @@ -0,0 +1,134 @@ +/* + * Project: UHC + * Class: gg.uhc.uhc.modules.timer.renderer.TabListRenderer + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Graham Howden . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package gg.uhc.uhc.modules.timer.renderer; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.*; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.google.common.base.Optional; +import org.bukkit.plugin.Plugin; + +public class TabListRenderer implements TimerRenderer { + + protected static final WrappedChatComponent CLEAR_BAR_JSON = WrappedChatComponent.fromJson("{\"translate\":\"\"}"); + protected static final PacketType PACKET_TYPE = PacketType.Play.Server.PLAYER_LIST_HEADER_FOOTER; + protected static final int TOP_INDEX = 0; + protected static final int BOTTOM_INDEX = 1; + + protected final ProtocolManager manager; + + protected final int writeIndex; + protected final int clearIndex; + + protected WrappedChatComponent[] lastInterceptedMessages = new WrappedChatComponent[] { + CLEAR_BAR_JSON, + CLEAR_BAR_JSON + }; + + protected WrappedChatComponent lastSentMessage; + + public TabListRenderer(Plugin plugin, ProtocolManager manager, TabListPosition position) { + this.manager = manager; + + if (position == TabListPosition.TOP) { + writeIndex = TOP_INDEX; + clearIndex = BOTTOM_INDEX; + } else { + writeIndex = BOTTOM_INDEX; + clearIndex = TOP_INDEX; + } + + manager.addPacketListener(new TabListListener(plugin)); + } + + protected void sendBarMessage(String message) { + lastSentMessage = WrappedChatComponent.fromText(message); + final PacketContainer container = this.manager.createPacket(PACKET_TYPE); + + container.getChatComponents() + .write(writeIndex, lastSentMessage) + .write(clearIndex, lastInterceptedMessages[clearIndex]); + + this.manager.broadcastServerPacket(container); + } + + protected void removeBar() { + lastSentMessage = null; + + final PacketContainer container = this.manager.createPacket(PACKET_TYPE); + + container.getChatComponents() + .write(TOP_INDEX, lastInterceptedMessages[TOP_INDEX]) + .write(BOTTOM_INDEX, lastInterceptedMessages[BOTTOM_INDEX]); + + this.manager.broadcastServerPacket(container); + } + + @Override + public void onStart(String message) { + sendBarMessage(message); + } + + @Override + public void onUpdate(String message, double progress) { + sendBarMessage(message); + } + + @Override + public void onStop() { + removeBar(); + } + + class TabListListener extends PacketAdapter { + TabListListener(Plugin plugin) { + super(plugin, PACKET_TYPE); + } + + @Override + public void onPacketSending(PacketEvent event) { + final StructureModifier components = event.getPacket().getChatComponents(); + + final WrappedChatComponent[] intercepted = new WrappedChatComponent[] { + Optional + .fromNullable(components.readSafely(TOP_INDEX)) + .or(CLEAR_BAR_JSON), + Optional + .fromNullable(components.readSafely(BOTTOM_INDEX)) + .or(CLEAR_BAR_JSON) + }; + + // Only write if it wasn't one of our timer messages + if (lastSentMessage == null || !intercepted[writeIndex].equals(lastSentMessage)) { + lastInterceptedMessages[TOP_INDEX] = intercepted[TOP_INDEX]; + lastInterceptedMessages[BOTTOM_INDEX] = intercepted[BOTTOM_INDEX]; + } + } + } +}