mirror of
https://github.com/rockyhawk64/CommandPanels.git
synced 2025-11-18 07:14:17 +01:00
Compare commits
9 Commits
451867be70
...
589d9cc046
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
589d9cc046 | ||
|
|
733e7396dd | ||
|
|
580dd3e0ef | ||
|
|
4172e4866e | ||
|
|
ba489b1128 | ||
|
|
01c242ac3b | ||
|
|
df90aba890 | ||
|
|
e4cacd5e4e | ||
|
|
1001d8b6b8 |
107
README.md
107
README.md
@ -1,61 +1,114 @@
|
||||

|
||||
|
||||
## Trusted GUI Plugin since 2019
|
||||
## 🏆 Trusted GUI Plugin since 2019
|
||||
|
||||
CommandPanels makes it easy to create interactive menus without coding.
|
||||
From shops and quests to server tools, you can design custom GUIs with YAML or the online editor.
|
||||
CommandPanels isn't like other menu plugins it's a **GUI framework**. it gives you true dynamic control through **logic**, **data**, and **panel variety**.
|
||||
From shops and quests to custom tools and admin panels, you can design powerful GUIs using YAML or the online editor.
|
||||
|
||||
- ✅ Actively maintained for 6+ years
|
||||
- 🖼️ Online editor for faster menu building
|
||||
- 🌍 Cross-platform support (Java + Bedrock)
|
||||
- 📘 Full docs and community Discord
|
||||
- 🖼️ Online editor for rapid menu building
|
||||
- 🧠 Advanced logic and data support
|
||||
- 🌍 Cross-platform (Java + Bedrock)
|
||||
- 📘 Full documentation and community Discord
|
||||
|
||||
---
|
||||
|
||||
## Useful Links
|
||||
|
||||
### [📘 Documentation](https://docs.commandpanels.net)
|
||||
### [🛠️ Online Editor](https://commandpanels.net/editor)
|
||||
### [💬 Join Discord](https://discord.gg/WFQMTZxa53)
|
||||
- [📘 **Documentation**](https://docs.commandpanels.net)
|
||||
- [🛠️ **Online Editor**](https://commandpanels.net/editor)
|
||||
- [💬 **Discord**](https://discord.gg/WFQMTZxa53)
|
||||
|
||||
---
|
||||
|
||||
## Online Editor
|
||||
The online editor includes support for all three panel types. This means you can visually create and edit Dialog, Inventory, and Floodgate panels with ease, previews are not 1:1 but will significantly speed up development and testing.
|
||||
|
||||
The online editor includes support for **all three panel types**.
|
||||
It’s not just a basic form builder, it’s a **live YAML editor** with structure checks and visual previews, helping you work faster and avoid YAML errors.
|
||||
|
||||

|
||||
|
||||
- ⚡ Visual layout builder with slot highlighting
|
||||
- 🧹 Automatic indentation & structure checks
|
||||
- ✅ Works with Inventory, Dialog, and Floodgate panels
|
||||
- 🧠 Supports complex panels, not just simple menus
|
||||
|
||||
---
|
||||
|
||||
## Real Logic & Real Data
|
||||
|
||||
CommandPanels lets you build menus that **react to players**, not just display static items.
|
||||
|
||||
- Inline `$AND`, `$OR`, `$NOT` operators and grouping
|
||||
- Multiple items in a single slot with logical fallbacks
|
||||
- **Persistent** or **session-based data** usable anywhere, even in other plugins via PlaceholderAPI
|
||||
|
||||
**Example Condition:**
|
||||
~~~
|
||||
conditions: "$NOT (%player_name% $EQUALS Steve) $AND %vault_eco_balance% $ATLEAST 5000"
|
||||
~~~
|
||||
|
||||
This allows for **powerful, dynamic behavior** without scripting.
|
||||
|
||||
---
|
||||
|
||||
## GUI Types
|
||||
|
||||
### Inventory Panels *(can be used for complex GUI creation)*
|
||||
### Inventory Panels
|
||||
Create fully interactive GUIs for shops, kits, navigation menus, or custom tools.
|
||||
|
||||

|
||||
### Dialog Panels *(can be used for custom interfaces and requesting player input)*
|
||||
|
||||
---
|
||||
|
||||
### Dialog Panels
|
||||
Build structured, custom interfaces that can be used to request custom input.
|
||||
|
||||

|
||||
### Floodgate Panels *(can be used for Bedrock players on your server)*
|
||||
|
||||
---
|
||||
|
||||
### Floodgate Panels
|
||||
Bring **full GUI support to Bedrock players** using Geyser and Floodgate.
|
||||
CommandPanels is one of the only plugins that natively supports this.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## About
|
||||
Minecraft servers use graphical interfaces (GUIs) for a wide range of features,
|
||||
from server lobbies and player shops to quests and custom tools. CommandPanels provides a
|
||||
streamlined YAML scripting format designed to make GUI creation both accessible and highly customizable.
|
||||
|
||||
With built-in support for variables, persistent data, placeholders,
|
||||
conditions, and more, you can design interactive menus tailored to your server's needs without complex programming.
|
||||
Minecraft servers rely on GUIs for everything from shops and lobbies to quests and server tools.
|
||||
CommandPanels provides a **streamlined YAML scripting format** designed to make GUI creation both **accessible** and **deeply customizable**.
|
||||
|
||||
CommandPanels supports three distinct panel types, each with unique capabilities:
|
||||
|
||||
- Inventory Panels allow you to build item-based interfaces where players can interact with and manipulate items directly.
|
||||
- Dialog Panels are perfect for creating structured, menu-based conversations and navigation systems, ideal for quests or guided menus.
|
||||
- Floodgate Panels enable full GUI support for Bedrock Edition players through Geyser and Floodgate, ensuring cross-platform compatibility.
|
||||
- Inline logic & conditions
|
||||
- Dynamic placeholders
|
||||
- Persistent & session data
|
||||
- Full PlaceholderAPI support
|
||||
- Modern, clean codebase
|
||||
|
||||
Developed in Australia 🇦🇺
|
||||
|
||||
---
|
||||
|
||||
## Server Compatibility
|
||||
|
||||
This plugin is a **Paper** plugin, it will only work on Paper and its forks.
|
||||
CommandPanels is fully compatible with **Paper** and **Folia** servers.
|
||||
|
||||
---
|
||||
|
||||
## Our Partner
|
||||
|
||||

|
||||
|
||||
We have proudly partnered with [ReviveNode](http://billing.revivenode.com/aff.php?aff=379)!
|
||||
CommandPanels users have been offered 15% off on the first month by using the Promocode: **PANELS**
|
||||
CommandPanels users get **15% off their first month** with the promo code: **PANELS**
|
||||
|
||||
ReviveNode is a leading Minecraft hosting provider that offers affordable and high-quality server hosting solutions.
|
||||
Their focus on performance, reliability, and customer support makes them the perfect partner for CommandPanels.
|
||||
ReviveNode provides high-performance, reliable Minecraft hosting, making them the perfect partner for CommandPanels.
|
||||
|
||||
---
|
||||
|
||||
## Trusted by Servers Worldwide
|
||||
|
||||
For over 6 years, CommandPanels has powered **thousands of Minecraft servers**.
|
||||
Whether you're running survival, MMO, minigames, or custom networks, it can handle it.
|
||||
@ -1,6 +1,6 @@
|
||||
# |------------------------------------------------------------------------
|
||||
# | CommandPanels Dialog File
|
||||
# | Official Panel v2.0
|
||||
# | Official Panel v2
|
||||
# | https://www.spigotmc.org/resources/command-panels-custom-guis.67788/
|
||||
# |------------------------------------------------------------------------
|
||||
conditions: '%player_name% $HASPERM example.permission'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# |------------------------------------------------------------------------
|
||||
# | CommandPanels Custom Floodgate File
|
||||
# | Official Panel v2.0
|
||||
# | Official Panel v2
|
||||
# | https://www.spigotmc.org/resources/command-panels-custom-guis.67788/
|
||||
# |------------------------------------------------------------------------
|
||||
conditions: '%player_name% $HASPERM example.permission'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# |------------------------------------------------------------------------
|
||||
# | CommandPanels Simple Floodgate File
|
||||
# | Official Panel v2.0
|
||||
# | Official Panel v2
|
||||
# | https://www.spigotmc.org/resources/command-panels-custom-guis.67788/
|
||||
# |------------------------------------------------------------------------
|
||||
conditions: "%player_name% $HASPERM example.permission"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# |------------------------------------------------------------------------
|
||||
# | CommandPanels Inventory File
|
||||
# | Official Panel v2.0
|
||||
# | Official Panel v2
|
||||
# | https://www.spigotmc.org/resources/command-panels-custom-guis.67788/
|
||||
# |------------------------------------------------------------------------
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# |------------------------------------------------------------------------
|
||||
# | CommandPanels Config File
|
||||
# | By RockyHawk v6.0
|
||||
# | By RockyHawk v7
|
||||
# | https://www.spigotmc.org/resources/67788/
|
||||
# |
|
||||
# |------------------------------------------------------------------------
|
||||
@ -8,5 +8,8 @@
|
||||
# When disabled, custom commands to open panels will not be registered
|
||||
custom-commands: true
|
||||
|
||||
# Will make console logs when panels are opened and closed
|
||||
# Amount of time in ticks players must wait between opening panels
|
||||
cooldown-ticks: 5
|
||||
|
||||
# Will make console logs when panels are opened
|
||||
panel-snooper: false
|
||||
@ -1,5 +1,5 @@
|
||||
name: CommandPanels
|
||||
version: 4.0.12
|
||||
version: 4.1.0
|
||||
api-version: 1.21.9
|
||||
|
||||
main: me.rockyhawk.commandpanels.CommandPanels
|
||||
|
||||
@ -5,6 +5,7 @@ import me.rockyhawk.commandpanels.formatter.Placeholders;
|
||||
import me.rockyhawk.commandpanels.formatter.language.TextFormatter;
|
||||
import me.rockyhawk.commandpanels.formatter.data.DataLoader;
|
||||
import me.rockyhawk.commandpanels.interaction.openpanel.PanelOpenCommand;
|
||||
import me.rockyhawk.commandpanels.session.SessionDataUtils;
|
||||
import me.rockyhawk.commandpanels.session.inventory.generator.GenerateManager;
|
||||
import me.rockyhawk.commandpanels.session.inventory.listeners.ClickEvents;
|
||||
import me.rockyhawk.commandpanels.session.inventory.listeners.InventoryEvents;
|
||||
@ -38,6 +39,7 @@ public class Context {
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new InventoryEvents(this), plugin);
|
||||
Bukkit.getServer().getPluginManager().registerEvents(panelCommand, plugin);
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new ClickEvents(this), plugin);
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new SessionDataUtils(this), plugin);
|
||||
Bukkit.getServer().getPluginManager().registerEvents(generator, plugin);
|
||||
|
||||
// Register proxy channels
|
||||
|
||||
@ -45,15 +45,11 @@ public class TabComplete {
|
||||
|
||||
if (args.length == 2) {
|
||||
if (args[0].equalsIgnoreCase("open") && sender.hasPermission("commandpanels.command.open")) {
|
||||
if (sender instanceof Player player) {
|
||||
for (String panelName : ctx.plugin.panels.keySet()) {
|
||||
Panel panel = ctx.plugin.panels.get(panelName);
|
||||
if (panel.canOpen(player, ctx)) {
|
||||
output.add(panelName);
|
||||
}
|
||||
String prefix = args[1].toLowerCase();
|
||||
for (String panelName : ctx.plugin.panels.keySet()) {
|
||||
if (panelName.toLowerCase().contains(prefix)) {
|
||||
output.add(panelName);
|
||||
}
|
||||
} else {
|
||||
output.addAll(ctx.plugin.panels.keySet());
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("data") && sender.hasPermission("commandpanels.command.data")) {
|
||||
output.addAll(Arrays.asList("get", "set", "overwrite", "math", "del", "clear"));
|
||||
|
||||
@ -51,7 +51,7 @@ public class OpenCommand implements SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if(sender instanceof Player && !panel.canOpen((Player) sender, ctx)){
|
||||
if(sender instanceof Player && !panel.passesConditions((Player) sender, ctx)){
|
||||
ctx.text.sendError(sender, Message.COMMAND_NO_PERMISSION);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -111,6 +111,7 @@ public enum Message {
|
||||
|
||||
// Misc
|
||||
DIALOG_NO_BUTTONS("Dialog needs at least one button"),
|
||||
COOLDOWN_ERROR("You're opening panels too quickly"),
|
||||
TELEPORT_ERROR("Error with teleport tag"),
|
||||
REQUIRE_HEADDATABASE("Download the HeadDatabase plugin to use this feature!");
|
||||
|
||||
|
||||
@ -39,9 +39,7 @@ public class CommandRunner {
|
||||
}
|
||||
|
||||
public void runCommands(Panel panel, Player player, List<String> commands) {
|
||||
// Keep command execution thread safe
|
||||
Bukkit.getGlobalRegionScheduler().run(ctx.plugin, task ->
|
||||
runCommands(panel, player, commands, 0));
|
||||
runCommands(panel, player, commands, 0);
|
||||
}
|
||||
|
||||
private void runCommands(Panel panel, Player player, List<String> commands, int index) {
|
||||
|
||||
@ -59,7 +59,7 @@ public class PanelOpenCommand implements Listener {
|
||||
}
|
||||
|
||||
// Stop and do not open panel if conditions are false
|
||||
if (!panel.canOpen(e.getPlayer(), ctx)) {
|
||||
if (!panel.passesConditions(e.getPlayer(), ctx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,9 @@ package me.rockyhawk.commandpanels.session;
|
||||
import me.rockyhawk.commandpanels.Context;
|
||||
import me.rockyhawk.commandpanels.builder.logic.ConditionNode;
|
||||
import me.rockyhawk.commandpanels.builder.logic.ConditionParser;
|
||||
import me.rockyhawk.commandpanels.formatter.language.Message;
|
||||
import me.rockyhawk.commandpanels.session.inventory.InventoryPanel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -30,7 +33,8 @@ public abstract class Panel {
|
||||
this.type = config.getString("type", "inventory");
|
||||
}
|
||||
|
||||
public boolean canOpen(Player player, Context ctx) {
|
||||
// Check run for permission checks with commands
|
||||
public boolean passesConditions(Player player, Context ctx) {
|
||||
// Check the panel condition
|
||||
if (this.conditions.trim().isEmpty()) return true;
|
||||
try {
|
||||
@ -41,12 +45,32 @@ public abstract class Panel {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks for opening fresh panels
|
||||
public boolean canOpen(Player p, Context ctx) {
|
||||
// Do not open if user is in cooldown period
|
||||
NamespacedKey keyTime = new NamespacedKey(ctx.plugin, "last_open_time");
|
||||
Long lastOpen = p.getPersistentDataContainer().get(keyTime, PersistentDataType.LONG);
|
||||
long cooldown = ctx.fileHandler.config.getInt("cooldown-ticks") * 50L; // ticks in config, converted to millis
|
||||
if (lastOpen != null && System.currentTimeMillis() - lastOpen < cooldown) {
|
||||
ctx.text.sendError(p, Message.COOLDOWN_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not allow the same panel to be opened again if already open
|
||||
return !(p.getOpenInventory().getTopInventory().getHolder() instanceof InventoryPanel panel)
|
||||
|| !panel.getName().equals(getName());
|
||||
}
|
||||
|
||||
// Updates data to set the current panel and previous panel.
|
||||
public void updatePanelData(Context ctx, Player p) {
|
||||
NamespacedKey keyCurrent = new NamespacedKey(ctx.plugin, "current");
|
||||
NamespacedKey keyPrevious = new NamespacedKey(ctx.plugin, "previous");
|
||||
NamespacedKey keyTime = new NamespacedKey(ctx.plugin, "last_open_time");
|
||||
PersistentDataContainer container = p.getPersistentDataContainer();
|
||||
|
||||
// Time the player last opened any panel
|
||||
container.set(keyTime, PersistentDataType.LONG, System.currentTimeMillis());
|
||||
|
||||
// Move current → previous
|
||||
String current = container.get(keyCurrent, PersistentDataType.STRING);
|
||||
current = (current != null) ? current : "";
|
||||
@ -54,6 +78,9 @@ public abstract class Panel {
|
||||
|
||||
// Set this panel as the new current
|
||||
container.set(keyCurrent, PersistentDataType.STRING, this.name);
|
||||
if(ctx.fileHandler.config.getBoolean("panel-snooper")){
|
||||
ctx.text.sendInfo(Bukkit.getConsoleSender(), Message.PANEL_OPEN_LOG, p.getName(), this.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
33
src/me/rockyhawk/commandpanels/session/SessionDataUtils.java
Normal file
33
src/me/rockyhawk/commandpanels/session/SessionDataUtils.java
Normal file
@ -0,0 +1,33 @@
|
||||
package me.rockyhawk.commandpanels.session;
|
||||
|
||||
import me.rockyhawk.commandpanels.Context;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
public class SessionDataUtils implements Listener {
|
||||
|
||||
private final Context ctx;
|
||||
|
||||
public SessionDataUtils(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoinEvent(PlayerJoinEvent e) {
|
||||
removeSessionData(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuitEvent(PlayerQuitEvent e) {
|
||||
removeSessionData(e.getPlayer());
|
||||
}
|
||||
|
||||
private void removeSessionData(Player p){
|
||||
p.getPersistentDataContainer().getKeys().stream()
|
||||
.filter(key -> key.getNamespace().equalsIgnoreCase("commandpanels"))
|
||||
.forEach(key -> p.getPersistentDataContainer().remove(key));
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,10 @@ public class DialogPanel extends Panel {
|
||||
}
|
||||
|
||||
if(isNewPanelSession) {
|
||||
// Update panel data values
|
||||
// Don't open same panel if its already open
|
||||
if(!canOpen(player, ctx)){
|
||||
return;
|
||||
}
|
||||
updatePanelData(ctx, player);
|
||||
|
||||
// Run panel commands
|
||||
|
||||
@ -54,7 +54,10 @@ public class FloodgatePanel extends Panel {
|
||||
}
|
||||
|
||||
if(isNewPanelSession) {
|
||||
// Update panel data values
|
||||
// Don't open same panel if its already open
|
||||
if(!canOpen(player, ctx)){
|
||||
return;
|
||||
}
|
||||
updatePanelData(ctx, player);
|
||||
|
||||
// Run panel commands
|
||||
|
||||
@ -67,7 +67,7 @@ public class InventoryPanel extends Panel implements InventoryHolder {
|
||||
|
||||
if(isNewPanelSession) {
|
||||
// Don't open same panel if its already open
|
||||
if(checkCurrentPanel(player)){
|
||||
if(!canOpen(player, ctx)){
|
||||
return;
|
||||
}
|
||||
updatePanelData(ctx, player);
|
||||
@ -75,23 +75,17 @@ public class InventoryPanel extends Panel implements InventoryHolder {
|
||||
// Run panel commands
|
||||
CommandRunner runner = new CommandRunner(ctx);
|
||||
runner.runCommands(this, player, this.getCommands());
|
||||
|
||||
// Start a panel updater
|
||||
InventoryPanelUpdater updater = new InventoryPanelUpdater();
|
||||
updater.start(ctx, player, this);
|
||||
}
|
||||
|
||||
// Build and open the panel
|
||||
PanelBuilder builder = new InventoryPanelBuilder(ctx, player);
|
||||
builder.open(this);
|
||||
}
|
||||
|
||||
// Do not open the same panel again if its already open
|
||||
private boolean checkCurrentPanel(Player p) {
|
||||
if(p.getOpenInventory().getTopInventory().getHolder() instanceof InventoryPanel panel){
|
||||
return panel.getName().equals(getName());
|
||||
if(isNewPanelSession) {
|
||||
// Start a panel updater
|
||||
InventoryPanelUpdater updater = new InventoryPanelUpdater();
|
||||
updater.start(ctx, player, this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getRows() {
|
||||
|
||||
@ -15,34 +15,39 @@ import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public class InventoryPanelUpdater {
|
||||
|
||||
private ScheduledTask task;
|
||||
private ScheduledTask checkTask;
|
||||
private ScheduledTask updateTask;
|
||||
|
||||
/**
|
||||
* Panel updater will maintain itself with a checkTask that will end the updater
|
||||
* If it finds the panel has been closed it will end the updater tasks
|
||||
*/
|
||||
|
||||
public void start(Context ctx, Player p, InventoryPanel panel) {
|
||||
// If restarted, stop current event
|
||||
// Stop existing tasks if any
|
||||
stop();
|
||||
|
||||
InventoryPanelBuilder panelBuilder = new InventoryPanelBuilder(ctx, p);
|
||||
ItemBuilder builder = new ItemBuilder(ctx, panelBuilder);
|
||||
|
||||
// Determine update delay
|
||||
int updateDelay = 20;
|
||||
if (panel.getUpdateDelay().matches("\\d+")) {
|
||||
// Update delay value is a number
|
||||
updateDelay = Integer.parseInt(panel.getUpdateDelay());
|
||||
}
|
||||
|
||||
// If update delay is 0 then do not run the updater
|
||||
if (updateDelay == 0) {
|
||||
this.task = null;
|
||||
this.updateTask = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule repeating GUI update task on the player's region
|
||||
this.task = Bukkit.getRegionScheduler().runAtFixedRate(
|
||||
InventoryPanelBuilder panelBuilder = new InventoryPanelBuilder(ctx, p);
|
||||
ItemBuilder builder = new ItemBuilder(ctx, panelBuilder);
|
||||
|
||||
// Main update task
|
||||
this.updateTask = Bukkit.getRegionScheduler().runAtFixedRate(
|
||||
ctx.plugin,
|
||||
p.getLocation(),
|
||||
(scheduledTask) -> {
|
||||
Inventory inv = p.getOpenInventory().getTopInventory();
|
||||
|
||||
InventoryHolder holder = inv.getHolder();
|
||||
if (!(holder instanceof InventoryPanel) || holder != panel) {
|
||||
stop();
|
||||
@ -88,12 +93,32 @@ public class InventoryPanelUpdater {
|
||||
updateDelay,
|
||||
updateDelay
|
||||
);
|
||||
|
||||
// Fast heartbeat check task, should run frequently
|
||||
this.checkTask = Bukkit.getRegionScheduler().runAtFixedRate(
|
||||
ctx.plugin,
|
||||
p.getLocation(),
|
||||
(scheduledTask) -> {
|
||||
Inventory inv = p.getOpenInventory().getTopInventory();
|
||||
InventoryHolder holder = inv.getHolder();
|
||||
|
||||
if (!(holder instanceof InventoryPanel) || holder != panel) {
|
||||
stop();
|
||||
}
|
||||
},
|
||||
2,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
task = null;
|
||||
if (checkTask != null) {
|
||||
checkTask.cancel();
|
||||
checkTask = null;
|
||||
}
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel();
|
||||
updateTask = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user