From 3bdf8aa2fcdcd0eaf7bdfbb8085cd25737a3a561 Mon Sep 17 00:00:00 2001 From: Magic fTail Date: Mon, 17 Dec 2018 03:04:20 +0100 Subject: [PATCH] Add level up message for virtual levels --- .../main/java/net/runelite/api/ModelID.java | 54 ++++ .../plugins/virtuallevels/SkillModel.java | 101 +++++++ .../virtuallevels/VirtualLevelsConfig.java | 44 +++ .../virtuallevels/VirtualLevelsPlugin.java | 260 +++++++++++++++++- 4 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/ModelID.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/SkillModel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsConfig.java diff --git a/runelite-api/src/main/java/net/runelite/api/ModelID.java b/runelite-api/src/main/java/net/runelite/api/ModelID.java new file mode 100644 index 0000000000..0a2ea46816 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/ModelID.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Magic fTail + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api; + +public final class ModelID +{ + public static final int ANVIL = 1251; + public static final int WILLOW_TREE = 1570; + public static final int SLAYER_SKILL_MODEL = 1733; + public static final int FIREMAKING_SKILL_MODEL = 2260; + public static final int STEEL_KITESHIELD = 2339; + public static final int PURE_ESSENCE = 2349; + public static final int RAW_TUNA = 2355; + public static final int CLEAN_HERB = 2364; + public static final int HAMMER = 2376; + public static final int BLUE_WIZARD_HAT = 2483; + public static final int CHISEL = 2489; + public static final int HIGHWAYMAN_MASK = 2500; + public static final int STEEL_PICKAXE = 2529; + public static final int SHORTBOW = 2562; + public static final int STEEL_LONGSWORD = 2602; + public static final int STEEL_SWORD = 2604; + public static final int STEEL_ARROW = 2711; + public static final int PRAYER_SKILL_MODEL = 3325; + public static final int STRENGTH_SKILL_MODEL = 3327; + public static final int AGILITY_SKILL_MODEL = 3328; + public static final int HEARTH = 3326; + public static final int WATERING_CAN = 7331; + public static final int SAW = 12309; + public static final int FOOTPRINT = 19980; + public static final int COOKING_SKILL_MODEL = 27611; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/SkillModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/SkillModel.java new file mode 100644 index 0000000000..4076be73c7 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/SkillModel.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018, Magic fTail + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.virtuallevels; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import java.util.List; +import lombok.Getter; +import net.runelite.api.ModelID; +import net.runelite.api.Skill; + +@Getter +public enum SkillModel +{ + CONSTRUCTION1(Skill.CONSTRUCTION, ModelID.HAMMER, 10, 14, 669, 0, 15, 329), + CONSTRUCTION2(Skill.CONSTRUCTION, ModelID.SAW, 11, 14, 615, 0, 111, 451), + COOKING(Skill.COOKING, ModelID.COOKING_SKILL_MODEL, 31, 59, 169, 0, 1593, 963), + CRAFTING1(Skill.CRAFTING, ModelID.HAMMER, 30, 24, 418, 0, 14, 496), + CRAFTING2(Skill.CRAFTING, ModelID.CHISEL, 39, 45, 353, 0, 18, 400), + DEFENCE(Skill.DEFENCE, ModelID.STEEL_KITESHIELD, 34, 37, 337, 0, 1074, 598), + FARMING(Skill.FARMING, ModelID.WATERING_CAN, 31, 52, 118, 0, 1278, 451), + FIREMAKING(Skill.FIREMAKING, ModelID.FIREMAKING_SKILL_MODEL, 29, 55, 115, 0, 1689, 771), + FISHING(Skill.FISHING, ModelID.RAW_TUNA, 33, 30, 351, 0, 1865, 517), + FLETCHING1(Skill.FLETCHING, ModelID.STEEL_ARROW, 43, 19, 254, 0, 1257, 408), + FLETCHING2(Skill.FLETCHING, ModelID.STEEL_ARROW, 46, 44, 223, 0, 177, 444), + HERBLORE(Skill.HERBLORE, ModelID.CLEAN_HERB, 20, 35, 550, 0, 2024, 344), + HITPOINTS(Skill.HITPOINTS, ModelID.HEARTH, 35, 58, 538, 0, 0, 250), + MAGIC(Skill.MAGIC, ModelID.BLUE_WIZARD_HAT, 29, 50, 131, 0, 1913, 344), + MINING(Skill.MINING, ModelID.STEEL_PICKAXE, 38, 33, 292, 0, 1166, 413), + PRAYER(Skill.PRAYER, ModelID.PRAYER_SKILL_MODEL, 29, 27, 582, 0, 504, 505), + RANGED1(Skill.RANGED, ModelID.STEEL_ARROW, 28, 34, 206, 0, 195, 405), + RANGED2(Skill.RANGED, ModelID.SHORTBOW, 42, 17, 422, 0, 1618, 397), + RUNECRAFT(Skill.RUNECRAFT, ModelID.PURE_ESSENCE, 35, 38, 242, 0, 1979, 328), + SLAYER(Skill.SLAYER, ModelID.SLAYER_SKILL_MODEL, 34, 60, 221, 0, 1944, 649), + SMITHING(Skill.SMITHING, ModelID.ANVIL, 34, 53, 97, 0, 1868, 716), + STRENGTH(Skill.STRENGTH, ModelID.STRENGTH_SKILL_MODEL, 35, 23, 512, 0, 14, 631), + AGILITY(Skill.AGILITY, ModelID.AGILITY_SKILL_MODEL, 29, 29, 533, 0, 2040, 685), + THIEVING(Skill.THIEVING, ModelID.HIGHWAYMAN_MASK, 42, 31, 366, 0, 55, 155), + WOODCUTTING(Skill.WOODCUTTING, ModelID.WILLOW_TREE, 20, 69, 116, 0, 1978, 1800), + ATTACK1(Skill.ATTACK, ModelID.STEEL_SWORD, 65, 38, 234, 0, 148, 444), + ATTACK2(Skill.ATTACK, ModelID.STEEL_LONGSWORD, 27, 29, 198, 0, 1419, 330), + HUNTER(Skill.HUNTER, ModelID.FOOTPRINT, 45, 48, 512, 0, 0, 1000); + + private static final ListMultimap skillModels = ArrayListMultimap.create(); + + private final Skill skill; + private final int modelID; + private final int originalX; + private final int originalY; + private final int rotationX; + private final int rotationY; + private final int rotationZ; + private final int modelZoom; + + SkillModel(Skill skill, int modelID, int originalX, int originalY, int rotationX, int rotationY, int rotationZ, int modelZoom) + { + this.skill = skill; + this.modelID = modelID; + this.originalX = originalX; + this.originalY = originalY; + this.rotationX = rotationX; + this.rotationY = rotationY; + this.rotationZ = rotationZ; + this.modelZoom = modelZoom; + } + + static + { + for (SkillModel skillModel : values()) + { + skillModels.put(skillModel.skill, skillModel); + } + } + + public static List getSkillModels(Skill skill) + { + return skillModels.get(skill); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsConfig.java new file mode 100644 index 0000000000..83e611f737 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, Magic fTail + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.virtuallevels; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("virtualLvl") +public interface VirtualLevelsConfig extends Config +{ + @ConfigItem( + keyName = "virtualMessage", + name = "Enable level up message for virtual levels", + description = "Configures whether or not to show level up messages for virtual levels", + position = 0 + ) + default boolean virtualMessage() + { + return true; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsPlugin.java index 879110bb2b..05a2b597ab 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/virtuallevels/VirtualLevelsPlugin.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2018, Joshua Filby * Copyright (c) 2018, Jordan Atwood + * Copyright (c) 2018, Magic fTail * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,14 +26,37 @@ */ package net.runelite.client.plugins.virtuallevels; +import com.google.inject.Provides; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.Experience; +import net.runelite.api.FontID; +import net.runelite.api.GameState; +import net.runelite.api.ScriptID; import net.runelite.api.Skill; +import net.runelite.api.events.ExperienceChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; import net.runelite.api.events.ScriptCallbackEvent; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetPositionMode; +import net.runelite.api.widgets.WidgetSizeMode; +import net.runelite.api.widgets.WidgetTextAlignment; +import net.runelite.api.widgets.WidgetType; import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.PluginChanged; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -42,9 +66,11 @@ import net.runelite.client.plugins.PluginDescriptor; tags = {"skill", "total", "max"}, enabledByDefault = false ) -public class VirtualLevelsPlugin extends Plugin +public class VirtualLevelsPlugin extends Plugin implements KeyListener { private static final String TOTAL_LEVEL_TEXT_PREFIX = "Total level:
"; + private static final int X_OFFSET = 13; + private static final int Y_OFFSET = 16; @Inject private Client client; @@ -52,10 +78,33 @@ public class VirtualLevelsPlugin extends Plugin @Inject private ClientThread clientThread; + @Inject + private VirtualLevelsConfig config; + + @Inject + private EventBus eventBus; + + @Inject + private KeyManager keyManager; + + private final Map previousXpMap = new EnumMap<>(Skill.class); + + private final List skillsLeveledUp = new ArrayList<>(); + + private boolean closeMessage; + private boolean messageOpen; + + @Provides + VirtualLevelsConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(VirtualLevelsConfig.class); + } + @Override protected void shutDown() { clientThread.invoke(this::simulateSkillChange); + keyManager.unregisterKeyListener(this); } @Subscribe @@ -64,6 +113,7 @@ public class VirtualLevelsPlugin extends Plugin // this is guaranteed to be called after the plugin has been registered by the eventbus. startUp is not. if (pluginChanged.getPlugin() == this) { + keyManager.registerKeyListener(this); clientThread.invoke(this::simulateSkillChange); } } @@ -121,4 +171,212 @@ public class VirtualLevelsPlugin extends Plugin } } } + + private void buildVirtualLevelUp(Skill skill) + { + Widget chatboxContainer = client.getWidget(WidgetInfo.CHATBOX_CONTAINER); + + if (chatboxContainer == null) + { + return; + } + + String skillName = skill.getName(); + int skillLevel = Experience.getLevelForXp(client.getSkillExperience(skill)); + List skillModels = SkillModel.getSkillModels(skill); + String prefix = (skill == Skill.AGILITY || skill == Skill.ATTACK) ? "an " : "a "; + + Widget levelUpLevel = chatboxContainer.createChild(-1, WidgetType.TEXT); + Widget levelUpText = chatboxContainer.createChild(-1, WidgetType.TEXT); + Widget levelUpContinue = chatboxContainer.createChild(-1, WidgetType.TEXT); + + levelUpLevel.setText("Congratulations, you just advanced " + prefix + skillName + " level."); + levelUpLevel.setTextColor(0x000080); + levelUpLevel.setFontId(FontID.QUILL_8); + levelUpLevel.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpLevel.setOriginalX(73 + X_OFFSET); + levelUpLevel.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpLevel.setOriginalY(15 + Y_OFFSET); + levelUpLevel.setOriginalWidth(390); + levelUpLevel.setOriginalHeight(30); + levelUpLevel.setXTextAlignment(WidgetTextAlignment.CENTER); + levelUpLevel.setYTextAlignment(WidgetTextAlignment.LEFT); + levelUpLevel.setWidthMode(WidgetSizeMode.ABSOLUTE); + levelUpLevel.revalidate(); + + levelUpText.setText((skill == Skill.HITPOINTS ? "Your Hitpoints are now " + skillLevel : + "Your " + skillName + " level is now " + skillLevel) + "."); + levelUpText.setFontId(FontID.QUILL_8); + levelUpText.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpText.setOriginalX(73 + X_OFFSET); + levelUpText.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpText.setOriginalY(44 + Y_OFFSET); + levelUpText.setOriginalWidth(390); + levelUpText.setOriginalHeight(30); + levelUpText.setXTextAlignment(WidgetTextAlignment.CENTER); + levelUpText.setYTextAlignment(WidgetTextAlignment.LEFT); + levelUpText.setWidthMode(WidgetSizeMode.ABSOLUTE); + levelUpText.revalidate(); + + levelUpContinue.setText("Click here to continue"); + levelUpContinue.setTextColor(0x0000ff); + levelUpContinue.setFontId(FontID.QUILL_8); + levelUpContinue.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpContinue.setOriginalX(73 + X_OFFSET); + levelUpContinue.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpContinue.setOriginalY(74 + Y_OFFSET); + levelUpContinue.setOriginalWidth(390); + levelUpContinue.setOriginalHeight(17); + levelUpContinue.setXTextAlignment(WidgetTextAlignment.CENTER); + levelUpContinue.setYTextAlignment(WidgetTextAlignment.LEFT); + levelUpContinue.setWidthMode(WidgetSizeMode.ABSOLUTE); + levelUpContinue.setAction(0, "Continue"); + levelUpContinue.setOnOpListener((JavaScriptCallback) ev -> closeNextTick()); + levelUpContinue.setOnMouseOverListener((JavaScriptCallback) ev -> levelUpContinue.setTextColor(0xFFFFFF)); + levelUpContinue.setOnMouseLeaveListener((JavaScriptCallback) ev -> levelUpContinue.setTextColor(0x0000ff)); + levelUpContinue.setHasListener(true); + levelUpContinue.revalidate(); + + for (SkillModel skillModel : skillModels) + { + buildWidgetModel(chatboxContainer, skillModel); + } + + messageOpen = true; + } + + private void buildWidgetModel(Widget chatboxContainer, SkillModel model) + { + int iconWidth = 32; + int iconHeight = 32; + + if (model.getSkill() == Skill.CONSTRUCTION) + { + iconWidth = 49; + iconHeight = 61; + } + + Widget levelUpModel = chatboxContainer.createChild(-1, WidgetType.MODEL); + + levelUpModel.setModelId(model.getModelID()); + levelUpModel.setXPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpModel.setOriginalX(model.getOriginalX() + X_OFFSET); + levelUpModel.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + levelUpModel.setOriginalY(model.getOriginalY() + Y_OFFSET); + levelUpModel.setOriginalWidth(iconWidth); + levelUpModel.setOriginalHeight(iconHeight); + levelUpModel.setRotationX(model.getRotationX()); + levelUpModel.setRotationY(model.getRotationY()); + levelUpModel.setRotationZ(model.getRotationZ()); + levelUpModel.setModelZoom(model.getModelZoom()); + levelUpModel.revalidate(); + } + + private void closeNextTick() + { + if (!messageOpen) + { + return; + } + + Widget levelUpContinue = client.getWidget(WidgetInfo.CHATBOX_CONTAINER).getChild(2); + + levelUpContinue.setText("Please wait..."); + + messageOpen = false; + closeMessage = true; + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + if (event.getGameState() != GameState.LOGGED_IN) + { + return; + } + + for (Skill skill : Skill.values()) + { + previousXpMap.put(skill, client.getSkillExperience(skill)); + } + } + + @Subscribe + public void onExperienceChanged(ExperienceChanged event) + { + Skill skill = event.getSkill(); + + int xpAfter = client.getSkillExperience(skill); + int levelAfter = Experience.getLevelForXp(xpAfter); + + int xpBefore = previousXpMap.get(skill); + int levelBefore = Experience.getLevelForXp(xpBefore); + + previousXpMap.put(skill, xpAfter); + + if (!config.virtualMessage() || levelAfter < 100 || levelBefore >= levelAfter) + { + return; + } + + skillsLeveledUp.add(skill); + } + + @Subscribe + public void onGameTick(GameTick event) + { + if (closeMessage) + { + clientThread.invoke(() -> client.runScript( + ScriptID.RESET_CHATBOX_INPUT, + 1, + 1 + )); + + closeMessage = false; + } + + if (skillsLeveledUp.isEmpty()) + { + return; + } + + Widget chatboxContainer = client.getWidget(WidgetInfo.CHATBOX_CONTAINER); + + if (chatboxContainer != null && !chatboxContainer.isHidden()) + { + return; + } + + Skill skill = skillsLeveledUp.get(0); + + skillsLeveledUp.remove(skill); + + clientThread.invoke(() -> client.runScript(ScriptID.CLEAR_CHATBOX_PANEL)); + clientThread.invoke(() -> buildVirtualLevelUp(skill)); + } + + @Override + public void keyTyped(KeyEvent e) + { + if (e.getKeyChar() != ' ') + { + return; + } + + if (messageOpen) + { + closeNextTick(); + } + } + + @Override + public void keyPressed(KeyEvent e) + { + } + + @Override + public void keyReleased(KeyEvent e) + { + } }