Mirror von
https://github.com/TheSilentPro/HeadDB.git
synchronisiert 2024-12-26 19:02:39 +01:00
lots of refactoring (untested)
Dieser Commit ist enthalten in:
Ursprung
a57a61c904
Commit
1762d6a787
45
pom.xml
45
pom.xml
@ -75,13 +75,18 @@
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>SmartPlugin</artifactId>
|
||||
<version>e565e96a72</version>
|
||||
<version>1ade8879c3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>Warehouse</artifactId>
|
||||
<version>f40f72cb19</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.wesjd</groupId>
|
||||
<artifactId>anvilgui</artifactId>
|
||||
<version>1.5.3-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Soft Dependencies -->
|
||||
<dependency>
|
||||
@ -90,12 +95,6 @@
|
||||
<version>1.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.lokka30</groupId>
|
||||
<artifactId>treasury-api</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
@ -126,38 +125,6 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Buildnumber -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>buildnumber-maven-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-properties</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>create-metadata</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<doCheck>true</doCheck>
|
||||
<doUpdate>true</doUpdate>
|
||||
|
||||
<buildNumberPropertyName>build.number</buildNumberPropertyName>
|
||||
<revisionPropertyName>build.revision</revisionPropertyName>
|
||||
<scmBranchPropertyName>build.branch</scmBranchPropertyName>
|
||||
|
||||
<useLastCommittedRevision>false</useLastCommittedRevision>
|
||||
<shortRevisionLength>7</shortRevisionLength>
|
||||
<revisionOnScmFailure>0</revisionOnScmFailure>
|
||||
<items>
|
||||
<item>buildNumber</item>
|
||||
<item>scmVersion</item>
|
||||
</items>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Shade -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package tsp.headdb;
|
||||
|
||||
import tsp.headdb.core.command.CommandCategory;
|
||||
import tsp.headdb.core.command.CommandGive;
|
||||
import tsp.headdb.core.command.CommandHelp;
|
||||
import tsp.headdb.core.command.CommandInfo;
|
||||
import tsp.headdb.core.command.CommandLanguage;
|
||||
@ -11,6 +12,8 @@ import tsp.headdb.core.command.CommandSearch;
|
||||
import tsp.headdb.core.command.CommandSettings;
|
||||
import tsp.headdb.core.command.CommandTexture;
|
||||
import tsp.headdb.core.command.CommandUpdate;
|
||||
import tsp.headdb.core.economy.BasicEconomyProvider;
|
||||
import tsp.headdb.core.economy.VaultProvider;
|
||||
import tsp.headdb.core.listener.PlayerJoinListener;
|
||||
import tsp.headdb.core.storage.Storage;
|
||||
import tsp.headdb.core.task.UpdateTask;
|
||||
@ -23,6 +26,7 @@ import tsp.smartplugin.logger.PluginLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HeadDB extends SmartPlugin {
|
||||
|
||||
@ -30,20 +34,23 @@ public class HeadDB extends SmartPlugin {
|
||||
private PluginLogger logger;
|
||||
private BuildProperties buildProperties;
|
||||
private TranslatableLocalization localization;
|
||||
private BasicEconomyProvider economyProvider;
|
||||
private CommandManager commandManager;
|
||||
private Storage storage;
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
instance = this;
|
||||
instance.saveDefaultConfig();
|
||||
instance.logger = new PluginLogger(this, getConfig().getBoolean("debug"));
|
||||
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
|
||||
instance.buildProperties = new BuildProperties(this);
|
||||
instance.saveDefaultConfig();
|
||||
|
||||
new UpdateTask(getConfig().getLong("refresh", 3600L)).schedule(this);
|
||||
new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
|
||||
instance.logger.info("Loaded " + loadLocalization() + " languages!");
|
||||
|
||||
instance.initEconomy();
|
||||
|
||||
instance.storage = new Storage();
|
||||
//instance.storage.load();
|
||||
|
||||
@ -77,10 +84,27 @@ public class HeadDB extends SmartPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void initEconomy() {
|
||||
if (!getConfig().getBoolean("economy.enabled")) {
|
||||
instance.logger.debug("Economy disabled by config.yml!");
|
||||
instance.economyProvider = null;
|
||||
return;
|
||||
}
|
||||
|
||||
String raw = getConfig().getString("economy.provider", "VAULT");
|
||||
if (raw.equalsIgnoreCase("VAULT")) {
|
||||
economyProvider = new VaultProvider();
|
||||
}
|
||||
|
||||
economyProvider.init();
|
||||
instance.logger.info("Economy Provider: " + raw);
|
||||
}
|
||||
|
||||
private void loadCommands() {
|
||||
new CommandHelp().register();
|
||||
new CommandCategory().register();
|
||||
new CommandSearch().register();
|
||||
new CommandGive().register();
|
||||
new CommandUpdate().register();
|
||||
new CommandTexture().register();
|
||||
new CommandLanguage().register();
|
||||
@ -96,6 +120,10 @@ public class HeadDB extends SmartPlugin {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
public Optional<BasicEconomyProvider> getEconomyProvider() {
|
||||
return Optional.ofNullable(economyProvider);
|
||||
}
|
||||
|
||||
public TranslatableLocalization getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
@ -1,31 +1,116 @@
|
||||
package tsp.headdb.core.api;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.util.Utils;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
import tsp.headdb.implementation.head.HeadDatabase;
|
||||
import tsp.headdb.implementation.head.LocalHead;
|
||||
import tsp.headdb.implementation.requester.HeadProvider;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Head API for interacting with the main {@link HeadDatabase}.
|
||||
*
|
||||
* @author TheSilentPro (Silent)
|
||||
* @see HeadDatabase
|
||||
*/
|
||||
public final class HeadAPI {
|
||||
|
||||
/**
|
||||
* Utility class. No initialization nor extension.
|
||||
*/
|
||||
private HeadAPI() {}
|
||||
|
||||
/**
|
||||
* The main {@link HeadDatabase}.
|
||||
*/
|
||||
private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance(), HeadProvider.HEAD_STORAGE);
|
||||
|
||||
public static synchronized Optional<Head> getHeadById(int id) {
|
||||
/**
|
||||
* Retrieve a {@link List} of {@link Head} matching the name.
|
||||
*
|
||||
* @param name The name to match against
|
||||
* @param lenient Whether the filter should be lenient when matching
|
||||
* @return {@link List<Head> Heads}
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Head> getHeadsByName(String name, boolean lenient) {
|
||||
return getHeads().stream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link List} of {@link Head} matching the name.
|
||||
*
|
||||
* @param name The name to match against
|
||||
* @return {@link List<Head> Heads}
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Head> getHeadsByName(String name) {
|
||||
return getHeadsByName(name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Head} by its exact name.
|
||||
*
|
||||
* @param name The name to look for
|
||||
* @param lenient Whether the filter to be lenient when matching
|
||||
* @return The {@link Head}, else empty
|
||||
*/
|
||||
public static Optional<Head> getHeadByExactName(String name, boolean lenient) {
|
||||
return getHeads().stream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Head} by its exact name.
|
||||
*
|
||||
* @param name The name to look for
|
||||
* @return The {@link Head}, else empty
|
||||
*/
|
||||
@Nonnull
|
||||
public static Optional<Head> getHeadByExactName(String name) {
|
||||
return getHeadByExactName(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Head} by its id.
|
||||
*
|
||||
* @param id The id to look for
|
||||
* @return The {@link Head}, else empty
|
||||
*/
|
||||
@Nonnull
|
||||
public static Optional<Head> getHeadById(int id) {
|
||||
return getHeads().stream().filter(head -> head.getId() == id).findAny();
|
||||
}
|
||||
|
||||
public static synchronized Optional<Head> getHeadByTexture(String texture) {
|
||||
/**
|
||||
* Retrieve a {@link Head} by its texture value.
|
||||
*
|
||||
* @param texture The texture to look for
|
||||
* @return The {@link Head}, else empty
|
||||
*/
|
||||
@Nonnull
|
||||
public static Optional<Head> getHeadByTexture(String texture) {
|
||||
return getHeads().stream().filter(head -> head.getTexture().equals(texture)).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link List} of {@link Head} within the main {@link HeadDatabase}.
|
||||
*
|
||||
* @return {@link List<Head> Heads}
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Head> getHeads() {
|
||||
List<Head> result = new ArrayList<>();
|
||||
for (Category category : getHeadsMap().keySet()) {
|
||||
@ -35,18 +120,53 @@ public final class HeadAPI {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link List} of {@link Head} within a {@link Category}.
|
||||
*
|
||||
* @param category The category to retrieve the heads from
|
||||
* @return {@link List<Head> Heads}
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Head> getHeads(Category category) {
|
||||
return getHeadsMap().get(category);
|
||||
}
|
||||
|
||||
public static synchronized Map<Category, List<Head>> getHeadsMap() {
|
||||
/**
|
||||
* Retrieve an unmodifiable view of the database head map.
|
||||
*
|
||||
* @return The map
|
||||
*/
|
||||
@Nonnull
|
||||
public static Map<Category, List<Head>> getHeadsMap() {
|
||||
return Collections.unmodifiableMap(database.getHeads());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the total amount of {@link Head heads} present in the main {@link HeadDatabase}.
|
||||
*
|
||||
* @return Amount of heads
|
||||
*/
|
||||
public static int getTotalHeads() {
|
||||
return getHeads().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link Set} of local heads.
|
||||
* Note that this calculates the heads on every try.
|
||||
*
|
||||
* @return {@link Set<LocalHead> Local Heads}
|
||||
*/
|
||||
@Nonnull
|
||||
public static Set<LocalHead> getLocalHeads() {
|
||||
return Arrays.stream(Bukkit.getOfflinePlayers()).map(player -> new LocalHead(player.getUniqueId(), player.getName())).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the main {@link HeadDatabase} used by the plugin.
|
||||
*
|
||||
* @return {@link HeadDatabase Database}
|
||||
*/
|
||||
@Nonnull
|
||||
public static HeadDatabase getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
81
src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java
Normale Datei
81
src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java
Normale Datei
@ -0,0 +1,81 @@
|
||||
package tsp.headdb.core.api.event;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Called <strong>BEFORE</strong> a head is purchased but <strong>AFTER</strong> the transaction is complete.
|
||||
* This gives you the chance to cancel and refund the money.
|
||||
* <strong>This event is fired asynchronously!</strong>
|
||||
*
|
||||
* @author TheSilentPro (Silent)
|
||||
* @see tsp.headdb.core.util.Utils#processPayment(Player, Head)
|
||||
* @see Event#isAsynchronous()
|
||||
*/
|
||||
public class HeadPurchaseEvent extends Event implements Cancellable {
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
private Player player;
|
||||
private Head head;
|
||||
private BigDecimal cost;
|
||||
private final boolean success;
|
||||
private boolean cancelled;
|
||||
|
||||
public HeadPurchaseEvent(Player player, Head head, BigDecimal cost, boolean success) {
|
||||
super(true);
|
||||
this.player = player;
|
||||
this.head = head;
|
||||
this.cost = cost;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public Head getHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
public void setHead(Head head) {
|
||||
this.head = head;
|
||||
}
|
||||
|
||||
public BigDecimal getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(BigDecimal cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
@ -9,12 +9,14 @@ import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
import tsp.smartplugin.inventory.PagedPane;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommandCategory extends SubCommand {
|
||||
|
||||
public CommandCategory() {
|
||||
super("open", new String[]{"o"});
|
||||
super("open", Arrays.stream(Category.VALUES).map(Category::getName).collect(Collectors.toList()), "o");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -24,18 +26,23 @@ public class CommandCategory extends SubCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
getLocalization().sendMessage(player.getUniqueId(), "invalidArguments");
|
||||
return;
|
||||
}
|
||||
|
||||
Category.getByName(args[1]).ifPresentOrElse(category -> {
|
||||
boolean requirePermission = HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission");
|
||||
if (requirePermission && !player.hasPermission("headdb.category." + category.getName())) {
|
||||
if (requirePermission
|
||||
&& !player.hasPermission("headdb.category." + category.getName())
|
||||
&& !player.hasPermission("headdb.category.*")) {
|
||||
getLocalization().sendMessage(player.getUniqueId(), "noPermission");
|
||||
return;
|
||||
}
|
||||
|
||||
int page = 0;
|
||||
if (args.length >= 3) {
|
||||
try {
|
||||
page = Integer.parseInt(args[2]) - 1;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
page = Utils.resolveInt(args[2]) - 1;
|
||||
}
|
||||
|
||||
List<Head> heads = HeadAPI.getHeads(category);
|
||||
|
57
src/main/java/tsp/headdb/core/command/CommandGive.java
Normale Datei
57
src/main/java/tsp/headdb/core/command/CommandGive.java
Normale Datei
@ -0,0 +1,57 @@
|
||||
package tsp.headdb.core.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import tsp.headdb.core.api.HeadAPI;
|
||||
import tsp.headdb.core.util.Utils;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommandGive extends SubCommand {
|
||||
|
||||
public CommandGive() {
|
||||
super("give", HeadAPI.getHeads().stream().map(Head::getName).collect(Collectors.toList()), "g");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(CommandSender sender, String[] args) {
|
||||
// /hdb give <id> <player> [amount]
|
||||
if (args.length < 3) {
|
||||
getLocalization().sendMessage(sender, "invalidArguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = args.length >= 4 ? Utils.resolveInt(args[3]) : 1;
|
||||
|
||||
Optional<Head> head;
|
||||
String id = args[1];
|
||||
if (id.startsWith("t:")) {
|
||||
head = HeadAPI.getHeadByTexture(id.substring(2));
|
||||
} else {
|
||||
try {
|
||||
head = HeadAPI.getHeadById(Integer.parseInt(id));
|
||||
} catch (NumberFormatException nfe) {
|
||||
// Attempt to find it by exact name (useful for tab completions)
|
||||
head = HeadAPI.getHeadByExactName(id);
|
||||
}
|
||||
}
|
||||
|
||||
Player player = Bukkit.getPlayer(args[2]);
|
||||
if (player == null) {
|
||||
getLocalization().sendMessage(sender, "invalidTarget", msg -> msg.replace("%name%", args[2]));
|
||||
return;
|
||||
}
|
||||
|
||||
head.ifPresentOrElse(
|
||||
value -> {
|
||||
player.getInventory().addItem(value.getItem(player.getUniqueId()));
|
||||
getLocalization().sendMessage(sender, "giveCommand", msg -> msg.replace("%size%", String.valueOf(amount)).replace("%name%", value.getName()).replace("%receiver%", player.getName()));
|
||||
},
|
||||
() -> getLocalization().sendMessage(sender, "giveCommandInvalid", msg -> msg.replace("%name%", id))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,7 @@ import tsp.smartplugin.player.PlayerUtils;
|
||||
public class CommandHelp extends SubCommand {
|
||||
|
||||
public CommandHelp() {
|
||||
super("help", new String[]{"h"});
|
||||
super("help", "h");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -18,6 +18,7 @@ public class CommandHelp extends SubCommand {
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9info(i) &7- Show plugin information.");
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9open(o) &c<category> &b[page] &7- Open a specific category.");
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9search(s) &b(id:|tg:)&c<query> &7- Search for specific heads.");
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9give(g) &b(t:)&c<id> <player> &b[amount] &7- Give the player a specific head.");
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9update(u) &7- Manually update the database.");
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9language(l) &7- Change your language.");
|
||||
PlayerUtils.sendMessage(sender, "&7/hdb &9settings(st) &7- Open the settings menu.");
|
||||
|
@ -8,14 +8,14 @@ import tsp.smartplugin.player.PlayerUtils;
|
||||
public class CommandInfo extends SubCommand {
|
||||
|
||||
public CommandInfo() {
|
||||
super("info", new String[]{"i"});
|
||||
super("info", "i");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(CommandSender sender, String[] args) {
|
||||
if (HeadDB.getInstance().getConfig().getBoolean("showAdvancedPluginInfo")) {
|
||||
BuildProperties build = HeadDB.getInstance().getBuildProperties();
|
||||
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion() + " &7(&6Build " + build.getBuildNumber() + "&7)");
|
||||
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion());
|
||||
PlayerUtils.sendMessage(sender, "&7Created by &6" + HeadDB.getInstance().getDescription().getAuthors());
|
||||
PlayerUtils.sendMessage(sender, "&7Compiled on &6" + build.getTimestamp() + " &7by &6" + build.getAuthor());
|
||||
} else {
|
||||
|
@ -2,32 +2,26 @@ package tsp.headdb.core.command;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import tsp.smartplugin.localization.Message;
|
||||
import tsp.headdb.HeadDB;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class CommandLanguage extends SubCommand {
|
||||
|
||||
public CommandLanguage() {
|
||||
super("language", new String[]{"l", "lang"});
|
||||
super("language", HeadDB.getInstance().getLocalization().getData().keySet(), "l", "lang");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(CommandSender sender, String[] args) {
|
||||
String lang = "en";
|
||||
if (args.length < 1) {
|
||||
getLocalization().sendMessage(new Message()
|
||||
.receiver(sender)
|
||||
.text("invalidArguments")
|
||||
);
|
||||
if (args.length < 2) {
|
||||
getLocalization().sendMessage(sender, "invalidArguments");
|
||||
return;
|
||||
}
|
||||
String lang = args[1];
|
||||
|
||||
if (!getLocalization().getData().containsKey(lang)) {
|
||||
getLocalization().sendMessage(new Message()
|
||||
.receiver(sender)
|
||||
.text("invalidLanguage")
|
||||
.function(msg -> msg.replace("%languages%", toString(getLocalization().getData().keySet()))));
|
||||
getLocalization().sendMessage(sender, "invalidLanguage", msg -> msg.replace("%languages%", toString(getLocalization().getData().keySet())));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -37,7 +31,7 @@ public class CommandLanguage extends SubCommand {
|
||||
getLocalization().setLanguage(player.getUniqueId(), lang);
|
||||
}
|
||||
|
||||
getLocalization().sendMessage(new Message().receiver(sender).text("languageChanged").function(msg -> msg.replace("%language%", lang)));
|
||||
getLocalization().sendMessage(sender, "languageChanged", msg -> msg.replace("%language%", lang));
|
||||
}
|
||||
|
||||
private String toString(Set<String> set) {
|
||||
|
@ -1,30 +1,42 @@
|
||||
package tsp.headdb.core.command;
|
||||
|
||||
import net.wesjd.anvilgui.AnvilGUI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.RemoteConsoleCommandSender;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.api.HeadAPI;
|
||||
import tsp.headdb.core.util.Utils;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
import tsp.smartplugin.inventory.Button;
|
||||
import tsp.smartplugin.inventory.PagedPane;
|
||||
import tsp.smartplugin.inventory.Pane;
|
||||
import tsp.smartplugin.localization.TranslatableLocalization;
|
||||
import tsp.smartplugin.utils.Validate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommandMain extends HeadDBCommand implements CommandExecutor {
|
||||
public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
|
||||
|
||||
public CommandMain() {
|
||||
super("headdb", "headdb.command.open");
|
||||
super(
|
||||
"headdb",
|
||||
"headdb.command.open",
|
||||
HeadDB.getInstance().getCommandManager().getCommandsMap().values().stream().map(HeadDBCommand::getName).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -37,7 +49,7 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
if (!player.hasPermission(getPermission())) {
|
||||
sendMessage(sender, "noPermission");
|
||||
localization.sendMessage(sender, "noPermission");
|
||||
return;
|
||||
}
|
||||
localization.sendMessage(player.getUniqueId(), "openDatabase");
|
||||
@ -45,13 +57,30 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
|
||||
Pane pane = new Pane(6, Utils.translateTitle(localization.getMessage(player.getUniqueId(), "menu.main.title").orElse("&cHeadDB &7(" + HeadAPI.getTotalHeads() + ")"), HeadAPI.getTotalHeads(), "Main"));
|
||||
for (Category category : Category.VALUES) {
|
||||
pane.addButton(new Button(category.getItem(player.getUniqueId()), e -> {
|
||||
e.setCancelled(true);
|
||||
if (e.isLeftClick()) {
|
||||
Bukkit.dispatchCommand(e.getWhoClicked(), "hdb open " + category.getName());
|
||||
} else if (e.isRightClick()) {
|
||||
// todo: specific page
|
||||
new AnvilGUI.Builder()
|
||||
.onComplete((p, text) -> {
|
||||
try {
|
||||
int page = Integer.parseInt(text);
|
||||
// Remove when AnvilGUI adds option to return a void response
|
||||
List<Head> heads = HeadAPI.getHeads(category);
|
||||
PagedPane main = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.category.name").orElse(category.getName()), heads.size(), category.getName()));
|
||||
Utils.addHeads(player, category, main, heads);
|
||||
main.selectPage(page);
|
||||
main.reRender();
|
||||
return AnvilGUI.Response.openInventory(main.getInventory());
|
||||
} catch (NumberFormatException nfe) {
|
||||
return AnvilGUI.Response.text("Invalid number...");
|
||||
}
|
||||
})
|
||||
.text("1")
|
||||
.title(localization.getMessage(player.getUniqueId(), "menu.main.category.page.name").orElse("Enter page"))
|
||||
.plugin(HeadDB.getInstance())
|
||||
.open(player);
|
||||
}
|
||||
|
||||
e.setCancelled(true);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -66,19 +95,7 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
command.handle(sender, args);
|
||||
}, () -> sendMessage(sender, "invalidSubCommand"));
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
private void sendMessage(CommandSender sender, String key) {
|
||||
Validate.notNull(sender, "Message sender can not be null!");
|
||||
Validate.notNull(key, "Key can not be null!");
|
||||
|
||||
if (sender instanceof Player player) {
|
||||
localization.sendMessage(player.getUniqueId(), key);
|
||||
} else if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender){
|
||||
localization.sendConsoleMessage(key);
|
||||
}
|
||||
}, () -> localization.sendMessage(sender, "invalidSubCommand"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -88,4 +105,24 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
PluginCommand provided = HeadDB.getInstance().getCommand(label);
|
||||
if (provided == null || !provided.getName().equalsIgnoreCase(getName())) {
|
||||
return new ArrayList<>(); // not this command
|
||||
}
|
||||
|
||||
if (args.length == 0) {
|
||||
return new ArrayList<>(getCompletions());
|
||||
} else {
|
||||
Optional<SubCommand> sub = HeadDB.getInstance().getCommandManager().getCommand(args[1]);
|
||||
if (sub.isPresent()) {
|
||||
return new ArrayList<>(sub.get().getCompletions());
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import java.util.List;
|
||||
public class CommandSearch extends SubCommand {
|
||||
|
||||
public CommandSearch() {
|
||||
super("search", new String[]{"s"});
|
||||
super("search", "s");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -23,6 +23,11 @@ public class CommandSearch extends SubCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
getLocalization().sendMessage(player, "invalidArguments");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
builder.append(args[i]);
|
||||
|
@ -16,7 +16,7 @@ import java.util.Set;
|
||||
public class CommandSettings extends SubCommand {
|
||||
|
||||
public CommandSettings() {
|
||||
super("settings", new String[]{"st"});
|
||||
super("settings", "st");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -32,16 +32,22 @@ public class CommandSettings extends SubCommand {
|
||||
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.name").orElse("&cLanguage"))
|
||||
.setLore(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.available").orElse("&7Languages Available: &e%size%").replace("%size%", String.valueOf(langs.size())))
|
||||
.build(), e -> {
|
||||
e.setCancelled(true);
|
||||
PagedPane langPane = new PagedPane(4, 6, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.title").orElse("&cHeadDB &7- &eSelect Language").replace("%languages%", "%size%"), langs.size(), "Selector: Language"));
|
||||
for (String lang : langs) {
|
||||
langPane.addButton(new Button(new ItemBuilder(Material.PAPER)
|
||||
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.format").orElse(ChatColor.YELLOW + lang).replace("%language%", lang))
|
||||
.build(), langEvent -> {
|
||||
e.setCancelled(true);
|
||||
getLocalization().setLanguage(player.getUniqueId(), lang);
|
||||
getLocalization().sendMessage(player.getUniqueId(), "languageChanged", msg -> msg.replace("%language%", lang));
|
||||
}));
|
||||
}
|
||||
|
||||
langPane.open(player);
|
||||
}));
|
||||
|
||||
pane.open(player);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import tsp.headdb.core.util.Utils;
|
||||
public class CommandTexture extends SubCommand {
|
||||
|
||||
public CommandTexture() {
|
||||
super("texture", new String[]{"t"});
|
||||
super("texture", "t");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -26,7 +26,7 @@ public class CommandTexture extends SubCommand {
|
||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(getLocalization().getMessage(player.getUniqueId(), "copyTexture").orElse("Click to copy!"))));
|
||||
component.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, texture));
|
||||
player.spigot().sendMessage(component);
|
||||
}), () -> getLocalization().sendMessage("itemNoTexture"));
|
||||
}), () -> getLocalization().sendMessage(sender,"itemNoTexture"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,27 +1,19 @@
|
||||
package tsp.headdb.core.command;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.api.HeadAPI;
|
||||
import tsp.smartplugin.localization.Message;
|
||||
|
||||
public class CommandUpdate extends SubCommand {
|
||||
|
||||
public CommandUpdate() {
|
||||
super("update", new String[]{"u"});
|
||||
super("update", "u");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(CommandSender sender, String[] args) {
|
||||
getLocalization().sendMessage(new Message()
|
||||
.receiver(sender)
|
||||
.text("updateDatabase")
|
||||
);
|
||||
|
||||
HeadAPI.getDatabase().update((time, heads) -> getLocalization().sendMessage(new Message()
|
||||
.receiver(sender)
|
||||
.text("updateDatabaseDone")
|
||||
.function(msg -> msg.replace("%size%", String.valueOf(heads.size()).replace("%time%", String.valueOf(time)))))
|
||||
);
|
||||
getLocalization().sendMessage(sender, "updateDatabase");
|
||||
HeadAPI.getDatabase().update((time, result) -> HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,20 +3,43 @@ package tsp.headdb.core.command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.smartplugin.localization.TranslatableLocalization;
|
||||
import tsp.smartplugin.utils.Validate;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class HeadDBCommand {
|
||||
|
||||
private static final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
|
||||
|
||||
private final String name;
|
||||
private final String permission;
|
||||
private final Collection<String> completions;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public HeadDBCommand(String name, String permission, Collection<String> completions) {
|
||||
Validate.notNull(name, "Name can not be null!");
|
||||
Validate.notNull(permission, "Permission can not be null!");
|
||||
Validate.notNull(completions, "Completions can not be null!");
|
||||
|
||||
public HeadDBCommand(String name, String permission) {
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.completions = completions;
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public HeadDBCommand(String name, String permission) {
|
||||
this(name, permission, new ArrayList<>());
|
||||
}
|
||||
|
||||
public abstract void handle(CommandSender sender, String[] args);
|
||||
|
||||
public Collection<String> getCompletions() {
|
||||
return completions;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package tsp.headdb.core.command;
|
||||
import tsp.headdb.HeadDB;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
// args[0] = sub-command | args[1+] = params
|
||||
@ -10,13 +12,17 @@ public abstract class SubCommand extends HeadDBCommand {
|
||||
|
||||
private final String[] aliases;
|
||||
|
||||
public SubCommand(String name, @Nullable String[] aliases) {
|
||||
super(name, "headdb.command." + name);
|
||||
public SubCommand(String name, Collection<String> completions, @Nullable String... aliases) {
|
||||
super(name, "headdb.command." + name, completions);
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public SubCommand(String name, String... aliases) {
|
||||
this(name, new ArrayList<>(), aliases);
|
||||
}
|
||||
|
||||
public SubCommand(String name) {
|
||||
this(name, null);
|
||||
this(name, (String[]) null);
|
||||
}
|
||||
|
||||
public Optional<String[]> getAliases() {
|
||||
|
20
src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java
Normale Datei
20
src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java
Normale Datei
@ -0,0 +1,20 @@
|
||||
package tsp.headdb.core.economy;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface BasicEconomyProvider {
|
||||
|
||||
CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost);
|
||||
|
||||
CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount);
|
||||
|
||||
default CompletableFuture<Boolean> purchase(Player player, BigDecimal amount) {
|
||||
return canPurchase(player, amount).thenCompose(result -> result ? withdraw(player, amount) : CompletableFuture.completedFuture(false));
|
||||
}
|
||||
|
||||
void init();
|
||||
|
||||
}
|
45
src/main/java/tsp/headdb/core/economy/VaultProvider.java
Normale Datei
45
src/main/java/tsp/headdb/core/economy/VaultProvider.java
Normale Datei
@ -0,0 +1,45 @@
|
||||
package tsp.headdb.core.economy;
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import tsp.headdb.HeadDB;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class VaultProvider implements BasicEconomyProvider {
|
||||
|
||||
private Economy economy;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost) {
|
||||
double effectiveCost = cost.doubleValue();
|
||||
return CompletableFuture.supplyAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0)); // Vault is really old...
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount) {
|
||||
double effectiveCost = amount.doubleValue();
|
||||
return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
if (!Bukkit.getServer().getPluginManager().isPluginEnabled("Vault")) {
|
||||
HeadDB.getInstance().getLog().error("Vault is not installed!");
|
||||
return;
|
||||
}
|
||||
|
||||
RegisteredServiceProvider<Economy> economyProvider = Bukkit.getServer().getServicesManager().getRegistration(Economy.class);
|
||||
if (economyProvider == null) {
|
||||
HeadDB.getInstance().getLog().error("Could not find vault economy provider!");
|
||||
return;
|
||||
}
|
||||
|
||||
economy = economyProvider.getProvider();
|
||||
}
|
||||
|
||||
}
|
9
src/main/java/tsp/headdb/core/hook/Hooks.java
Normale Datei
9
src/main/java/tsp/headdb/core/hook/Hooks.java
Normale Datei
@ -0,0 +1,9 @@
|
||||
package tsp.headdb.core.hook;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class Hooks {
|
||||
|
||||
public static PluginHook PAPI = new PluginHook(Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null);
|
||||
|
||||
}
|
3
src/main/java/tsp/headdb/core/hook/PluginHook.java
Normale Datei
3
src/main/java/tsp/headdb/core/hook/PluginHook.java
Normale Datei
@ -0,0 +1,3 @@
|
||||
package tsp.headdb.core.hook;
|
||||
|
||||
public record PluginHook(boolean enabled) {}
|
@ -2,6 +2,7 @@ package tsp.headdb.core.storage;
|
||||
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.util.Utils;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
import tsp.warehouse.storage.sql.SQLiteDataManager;
|
||||
|
||||
@ -32,7 +33,8 @@ public class HeadStorage extends SQLiteDataManager<Collection<Head>> {
|
||||
set.getString("name"),
|
||||
set.getString("texture"),
|
||||
set.getString("tags"),
|
||||
set.getString("updated")
|
||||
set.getString("updated"),
|
||||
Category.valueOf(set.getString("category"))
|
||||
));
|
||||
}
|
||||
|
||||
@ -51,13 +53,14 @@ public class HeadStorage extends SQLiteDataManager<Collection<Head>> {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Head head : data) {
|
||||
builder.append(String.format("(%d, %s, %s, %s, %s, %s),",
|
||||
builder.append(String.format("(%d, %s, %s, %s, %s, %s, %s),",
|
||||
head.getId(),
|
||||
head.getUniqueId().toString(),
|
||||
head.getName(),
|
||||
head.getTexture(),
|
||||
head.getTags(),
|
||||
head.getUpdated()
|
||||
head.getUpdated(),
|
||||
head.getCategory().name()
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -14,9 +14,6 @@ import java.io.InputStreamReader;
|
||||
public class BuildProperties {
|
||||
|
||||
private String version = "unknown";
|
||||
private int buildNumber = 0;
|
||||
private String revision = "unknown";
|
||||
private String branch = "unknown";
|
||||
private String timestamp = "unknown";
|
||||
private String author = "unknown";
|
||||
|
||||
@ -29,9 +26,6 @@ public class BuildProperties {
|
||||
|
||||
YamlConfiguration data = YamlConfiguration.loadConfiguration(new InputStreamReader(in));
|
||||
this.version = data.getString("version", "unknown");
|
||||
this.buildNumber = data.getInt("build", 0);
|
||||
this.revision = data.getString("buildRevision", "unknown");
|
||||
this.branch = data.getString("buildBranch", "unknown");
|
||||
this.timestamp = data.getString("buildTimestamp", "unknown");
|
||||
this.author = data.getString("buildAuthor", "unknown");
|
||||
}
|
||||
@ -40,18 +34,6 @@ public class BuildProperties {
|
||||
return version;
|
||||
}
|
||||
|
||||
public int getBuildNumber() {
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
public String getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public String getBranch() {
|
||||
return branch;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
@ -2,12 +2,17 @@ package tsp.headdb.core.util;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.api.HeadAPI;
|
||||
import tsp.headdb.core.api.event.HeadPurchaseEvent;
|
||||
import tsp.headdb.core.economy.BasicEconomyProvider;
|
||||
import tsp.headdb.core.hook.Hooks;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.head.Head;
|
||||
import tsp.smartplugin.inventory.Button;
|
||||
@ -18,10 +23,12 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class Utils {
|
||||
|
||||
@ -59,16 +66,16 @@ public class Utils {
|
||||
|
||||
public static PagedPane createPaged(Player player, String title) {
|
||||
PagedPane main = new PagedPane(4, 6, title);
|
||||
main.getInventory().clear();
|
||||
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").ifPresent(head -> main.setBackItem(head.getItem(player.getUniqueId())));
|
||||
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").ifPresent(head -> main.setCurrentItem(head.getItem(player.getUniqueId())));
|
||||
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").ifPresent(head -> main.setNextItem(head.getItem(player.getUniqueId())));
|
||||
|
||||
main.setControlCurrent(new Button(main.getCurrentItem(), e -> Bukkit.dispatchCommand(player, "hdb")));
|
||||
return main;
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public static void addHeads(Player player, @Nullable Category category, PagedPane pane, Collection<Head> heads) {
|
||||
pane.getInventory().clear();
|
||||
for (Head head : heads) {
|
||||
ItemStack item = head.getItem(player.getUniqueId());
|
||||
pane.addButton(new Button(item, e -> {
|
||||
@ -92,6 +99,42 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<Boolean> processPayment(Player player, Head head) {
|
||||
BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()));
|
||||
|
||||
Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider();
|
||||
if (optional.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
} else {
|
||||
return optional.get().purchase(player, cost).thenApply(success -> {
|
||||
HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
return !event.isCancelled() && success;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void purchase(Player player, Head head) {
|
||||
processPayment(player, head).whenComplete((success, ex) -> {
|
||||
if (ex != null) {
|
||||
HeadDB.getInstance().getLog().error("Failed to purchase head '" + head.getName() + "' for player: " + player.getName());
|
||||
ex.printStackTrace();
|
||||
} else {
|
||||
// Bukkit API, therefore task is ran sync. Needs testing not sure if this can be ran async.
|
||||
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
|
||||
player.getInventory().addItem(head.getItem(player.getUniqueId()));
|
||||
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
|
||||
if (Hooks.PAPI.enabled()) {
|
||||
command = PlaceholderAPI.setPlaceholders(player, command);
|
||||
}
|
||||
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<String> getTexture(ItemStack head) {
|
||||
ItemMeta meta = head.getItemMeta();
|
||||
if (meta == null) {
|
||||
@ -107,12 +150,21 @@ public class Utils {
|
||||
}
|
||||
|
||||
return profile.getProperties().get("textures").stream()
|
||||
.filter(p -> p.getValue().equals("textures"))
|
||||
.filter(p -> p.getName().equals("textures"))
|
||||
.findAny()
|
||||
.map(Property::getName);
|
||||
.map(Property::getValue);
|
||||
} catch (NoSuchFieldException | SecurityException | IllegalAccessException e ) {
|
||||
e.printStackTrace();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static int resolveInt(String raw) {
|
||||
try {
|
||||
return Integer.parseInt(raw);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,15 +2,19 @@ package tsp.headdb.implementation.category;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.api.HeadAPI;
|
||||
import tsp.headdb.core.util.Utils;
|
||||
import tsp.smartplugin.builder.item.ItemBuilder;
|
||||
import tsp.smartplugin.utils.StringUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum Category {
|
||||
|
||||
@ -52,14 +56,25 @@ public enum Category {
|
||||
public ItemStack getItem(UUID receiver) {
|
||||
if (item == null) {
|
||||
HeadAPI.getHeads(this).stream().findFirst()
|
||||
.ifPresentOrElse(head -> item = new ItemBuilder(head.getItem(receiver))
|
||||
.name(Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.category.name").orElse("&e" + getName()), HeadAPI.getHeads(this).size(), getName().toUpperCase(Locale.ROOT)))
|
||||
.setLore((String[]) null)
|
||||
.build(),
|
||||
.ifPresentOrElse(head -> {
|
||||
ItemStack retrieved = new ItemStack(head.getItem(receiver));
|
||||
ItemMeta meta = retrieved.getItemMeta();
|
||||
if (meta != null && meta.getLore() != null) {
|
||||
meta.setDisplayName(Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.main.category.name").orElse("&e" + getName()), HeadAPI.getHeads(this).size(), getName().toUpperCase(Locale.ROOT)));
|
||||
meta.setLore(HeadDB.getInstance().getConfig().getStringList("menu.main.category.lore").stream()
|
||||
.map(StringUtils::colorize)
|
||||
.collect(Collectors.toList()));
|
||||
retrieved.setItemMeta(meta);
|
||||
item = retrieved;
|
||||
} else {
|
||||
item = new ItemStack(Material.PLAYER_HEAD);
|
||||
HeadDB.getInstance().getLog().debug("Failed to get null-meta category item for: " + name());
|
||||
}
|
||||
},
|
||||
() -> item = new ItemBuilder(Material.PLAYER_HEAD).name(getName().toUpperCase(Locale.ROOT)).build());
|
||||
}
|
||||
|
||||
return item;
|
||||
return item; // Return clone that changes are not reflected
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.smartplugin.builder.item.ItemBuilder;
|
||||
import tsp.smartplugin.localization.TranslatableLocalization;
|
||||
import tsp.smartplugin.utils.Validate;
|
||||
@ -23,15 +24,17 @@ public class Head {
|
||||
private final String texture;
|
||||
private final String tags;
|
||||
private final String updated;
|
||||
private final Category category;
|
||||
private ItemStack item;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated) {
|
||||
public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated, Category category) {
|
||||
Validate.notNull(uniqueId, "Unique id can not be null!");
|
||||
Validate.notNull(name, "Name can not be null!");
|
||||
Validate.notNull(texture, "Texture can not be null!");
|
||||
Validate.notNull(tags, "Tags can not be null!");
|
||||
Validate.notNull(updated, "Updated can not be null!");
|
||||
Validate.notNull(category, "Category can not be null!");
|
||||
|
||||
this.id = id;
|
||||
this.uniqueId = uniqueId;
|
||||
@ -39,6 +42,7 @@ public class Head {
|
||||
this.texture = texture;
|
||||
this.tags = tags;
|
||||
this.updated = updated;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public ItemStack getItem(UUID receiver) {
|
||||
@ -65,7 +69,7 @@ public class Head {
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
|
||||
return item;
|
||||
return item.clone(); // Return clone that changes are not reflected
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
@ -92,4 +96,8 @@ public class Head {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public Category getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
}
|
@ -2,19 +2,14 @@ package tsp.headdb.implementation.head;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.requester.HeadProvider;
|
||||
import tsp.headdb.implementation.requester.Requester;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class HeadDatabase {
|
||||
|
||||
@ -28,7 +23,7 @@ public class HeadDatabase {
|
||||
this.plugin = plugin;
|
||||
this.scheduler = plugin.getServer().getScheduler();
|
||||
this.requester = new Requester(plugin, provider);
|
||||
this.heads = Collections.synchronizedMap(new EnumMap<>(Category.class));
|
||||
this.heads = new HashMap<>();
|
||||
}
|
||||
|
||||
public Map<Category, List<Head>> getHeads() {
|
||||
@ -49,9 +44,7 @@ public class HeadDatabase {
|
||||
|
||||
public void update(BiConsumer<Long, Map<Category, List<Head>>> fetched) {
|
||||
getHeadsNoCache((elapsed, result) -> {
|
||||
synchronized (heads) {
|
||||
heads.putAll(result);
|
||||
}
|
||||
heads.putAll(result);
|
||||
timestamp = System.currentTimeMillis();
|
||||
fetched.accept(elapsed, result);
|
||||
});
|
||||
|
5
src/main/java/tsp/headdb/implementation/head/LocalHead.java
Normale Datei
5
src/main/java/tsp/headdb/implementation/head/LocalHead.java
Normale Datei
@ -0,0 +1,5 @@
|
||||
package tsp.headdb.implementation.head;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record LocalHead(UUID uniqueId, String name) {}
|
@ -4,9 +4,9 @@ import tsp.headdb.implementation.category.Category;
|
||||
|
||||
public enum HeadProvider {
|
||||
|
||||
HEAD_API("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true"), // No ids
|
||||
HEAD_STORAGE("https://raw.githubusercontent.com/TheSilentPro/HeadStorage/master/storage/%s.json"),
|
||||
HEAD_WORKER(""), // TODO: implement
|
||||
HEAD_API("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true"),
|
||||
HEAD_WORKER(""), // Unimplemented yet.
|
||||
HEAD_ARCHIVE("https://heads.pages.dev/archive/%s.json");
|
||||
|
||||
private final String url;
|
||||
|
@ -48,7 +48,8 @@ public class Requester {
|
||||
obj.get("name").getAsString(),
|
||||
obj.get("value").getAsString(),
|
||||
obj.get("tags").getAsString(),
|
||||
response.date()
|
||||
response.date(),
|
||||
category
|
||||
));
|
||||
}
|
||||
|
||||
@ -56,8 +57,10 @@ public class Requester {
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name());
|
||||
provider = HeadProvider.HEAD_ARCHIVE;
|
||||
fetchAndResolve(category, heads);
|
||||
if (HeadDB.getInstance().getConfig().getBoolean("fallback") && provider != HeadProvider.HEAD_ARCHIVE) { // prevent recursion. Maybe switch to an attempts counter down in the future
|
||||
provider = HeadProvider.HEAD_ARCHIVE;
|
||||
fetchAndResolve(category, heads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# How often the database should be updated in seconds.
|
||||
refresh: 3600
|
||||
refresh: 86400
|
||||
|
||||
# If local heads should be enabled. Only starts keeping track of joined players when enabled!
|
||||
localHeads: true
|
||||
@ -11,27 +11,10 @@ requireCategoryPermission: false
|
||||
# If enabled, the menu will close after purchasing a head (even if the purchase fails)
|
||||
closeOnPurchase: false
|
||||
|
||||
# Hidden heads from the menu
|
||||
hidden:
|
||||
enabled: false
|
||||
# If enabled it will also hide any heads matching these tags in the favorites menu
|
||||
hideFavorites: true
|
||||
# If the head name matches any of the listen words it will be hidden (case-sensitive)
|
||||
names:
|
||||
- ""
|
||||
# If the head has one of the listed tags it will be hidden
|
||||
tags:
|
||||
- ""
|
||||
|
||||
# Economy options
|
||||
# Economy Options
|
||||
economy:
|
||||
enable: false
|
||||
# Supported: VAULT, TREASURY
|
||||
provider: "VAULT"
|
||||
# Providers like treasury support multiple currencies
|
||||
# Set the ID of the one used for head purchasing below.
|
||||
# Leave empty to use the primary currency or if the provider does not support multiple currencies
|
||||
currency: ""
|
||||
provider: "VAULT" # Supported: VAULT
|
||||
cost:
|
||||
alphabet: 100
|
||||
animals: 100
|
||||
@ -43,112 +26,14 @@ economy:
|
||||
miscellaneous: 100
|
||||
monsters: 100
|
||||
plants: 100
|
||||
local: 1000
|
||||
|
||||
# UI customization options.
|
||||
ui:
|
||||
category:
|
||||
# Head categories. You can use item: instead of head: here, but AIR is not supported.
|
||||
alphabet:
|
||||
location: 20
|
||||
head: 1788
|
||||
animals:
|
||||
location: 21
|
||||
head: 5741
|
||||
blocks:
|
||||
location: 22
|
||||
head: 8624
|
||||
decoration:
|
||||
location: 23
|
||||
head: 11046
|
||||
food-drinks:
|
||||
location: 24
|
||||
head: 17442
|
||||
humans:
|
||||
location: 29
|
||||
head: 19361
|
||||
humanoid:
|
||||
location: 30
|
||||
head: 28320
|
||||
miscellaneous:
|
||||
location: 31
|
||||
head: 32746
|
||||
monsters:
|
||||
location: 32
|
||||
head: 34819
|
||||
plants:
|
||||
location: 33
|
||||
head: 37278
|
||||
# Meta categories, used for UI elements. AIR is not supported. You can use head: instead of item: here.
|
||||
favorites:
|
||||
location: 39
|
||||
item: BOOK
|
||||
search:
|
||||
location: 40
|
||||
item: DARK_OAK_SIGN
|
||||
local:
|
||||
location: 41
|
||||
item: COMPASS
|
||||
# Item used to fill unused slots in the categories' menu. AIR is supported. You can use head: instead of item: here.
|
||||
fill:
|
||||
item: BLACK_STAINED_GLASS_PANE
|
||||
sound:
|
||||
# Whether the sounds should be played
|
||||
enabled: true
|
||||
|
||||
# Played when a head is taken with no economy plugin
|
||||
noEconomy:
|
||||
# The name of the sound.
|
||||
# Must be one from: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html
|
||||
name: ENTITY_EXPERIENCE_ORB_PICKUP
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when the player doesn't have permission for the menu
|
||||
noPermission:
|
||||
name: BLOCK_ANVIL_BREAK
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when they purchase a head
|
||||
paid:
|
||||
name: ENTITY_EXPERIENCE_ORB_PICKUP
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when the head is free
|
||||
free:
|
||||
name: ENTITY_EXPERIENCE_ORB_PICKUP
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when the player does not have enough funds for the head
|
||||
unavailable:
|
||||
name: BLOCK_ANVIL_BREAK
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when a category/menu is opened
|
||||
open:
|
||||
name: ENTITY_EXPERIENCE_ORB_PICKUP
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when a head is added to the favorites list
|
||||
addFavorite:
|
||||
name: ENTITY_EXPERIENCE_ORB_PICKUP
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Played when a head is removed to from favorites list
|
||||
removeFavorite:
|
||||
name: BLOCK_ANVIL_BREAK
|
||||
volume: 1
|
||||
pitch: 1
|
||||
|
||||
# Command Configuration
|
||||
commands:
|
||||
# Commands to run ONLY if the purchase is successful.
|
||||
# They are run as CONSOLE after the player has receiver the head in their inventory.
|
||||
purchase:
|
||||
- ""
|
||||
|
||||
# The head provider used.
|
||||
# Don't touch this if you don't know what you are doing!
|
||||
headProvider: "HEAD_STORAGE"
|
||||
|
||||
# If the original fetching fails and this is enabled,
|
||||
# the plugin will attempt to fetch the heads from an archive.
|
||||
# The archive is static (manually updated) so some heads may be missing, this will only be used when all else fails.
|
||||
|
@ -2,14 +2,17 @@ noConsole: "&cOnly for in-game players!"
|
||||
noPermission: "&cNo permission!"
|
||||
invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help."
|
||||
invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help."
|
||||
invalidTarget: "&cInvalid target: &e%name%"
|
||||
invalidCategory: "&cInvalid Category!"
|
||||
invalidNumber: "&e%name% &cis not a number!"
|
||||
invalidPageIndex: "&cThat page is out of bounds! Max: %pages%"
|
||||
|
||||
openDatabase: "&7Opening &cHeadDatabase&7..."
|
||||
openDatabase: "" # Intentionally empty. Sent when the main gui is opened
|
||||
updateDatabase: "&7Updating..."
|
||||
updateDatabaseDone: "&7Database was updated! Heads: &6%size% &7| Took: &a%time%"
|
||||
searchCommand: "&7Searching for heads matching: &e%query%"
|
||||
searchCommandResults: "&7Found &e%size% &7matches!"
|
||||
giveCommand: "&7Gave &ex%size% %name% &7to &e%receiver%"
|
||||
giveCommandInvalid: "&cInvalid head: &e%name%"
|
||||
itemTexture: "&7Texture: %texture%"
|
||||
itemNoTexture: "&cThis item does not have a texture!"
|
||||
copyTexture: "&6Click to copy texture!"
|
||||
@ -22,14 +25,21 @@ menu:
|
||||
title: "&cHeadDB &7(%size%)"
|
||||
category:
|
||||
name: "&e&l%category%"
|
||||
page:
|
||||
name: "&eGo to specific page"
|
||||
lore:
|
||||
- "&7Left-Click to open"
|
||||
- "&7Right-Click to open specific page"
|
||||
search:
|
||||
name: "&eSearch"
|
||||
category:
|
||||
name: "&e%category% &7(%size%)"
|
||||
name: "&cHeadDB &7- &e%category% &7(%size%)"
|
||||
head:
|
||||
name: "&e%name%"
|
||||
search:
|
||||
name: "&cHeadDB &7- &eSearch: %query% &7(%size%)"
|
||||
page:
|
||||
name: "&cHeadDB &7- &eEnter page number"
|
||||
settings:
|
||||
name: "&cHeadDB &7- &eSettings"
|
||||
language:
|
||||
|
@ -8,18 +8,16 @@ api-version: 1.19
|
||||
author: TheSilentPro (Silent)
|
||||
spigot-id: 84967
|
||||
|
||||
build: ${build.number}
|
||||
buildRevision: ${build.revision}
|
||||
buildBranch: ${build.branch}
|
||||
buildTimestamp: ${build.timestamp}
|
||||
buildAuthor: ${build.author}
|
||||
|
||||
libraries:
|
||||
- "com.google.code.gson:gson:2.10"
|
||||
# Although not up-to-date, spigot already includes gson. This is here just in case.
|
||||
#libraries:
|
||||
# - "com.google.code.gson:gson:2.10"
|
||||
|
||||
commands:
|
||||
headdb:
|
||||
usage: /headdb <arguments>
|
||||
usage: /headdb help
|
||||
description: Open the database
|
||||
aliases: [hdb, headdatabase, headmenu]
|
||||
|
||||
@ -27,27 +25,24 @@ permissions:
|
||||
headdb.admin:
|
||||
default: op
|
||||
children:
|
||||
headdb.open: true
|
||||
headdb.search: true
|
||||
headdb.give: true
|
||||
headdb.command.open: true
|
||||
headdb.command.search: true
|
||||
headdb.command.give: true
|
||||
headdb.command.update: true
|
||||
headdb.favorites: true
|
||||
headdb.local: true
|
||||
headdb.tagsearch: true
|
||||
headdb.update: true
|
||||
headdb.reload: true
|
||||
headdb.open:
|
||||
headdb.category.*: true
|
||||
headdb.command.open:
|
||||
default: op
|
||||
headdb.search:
|
||||
headdb.command.search:
|
||||
default: op
|
||||
headdb.give:
|
||||
headdb.command.give:
|
||||
default: op
|
||||
headdb.command.update:
|
||||
default: op
|
||||
headdb.favorites:
|
||||
default: op
|
||||
headdb.local:
|
||||
default: op
|
||||
headdb.tagsearch:
|
||||
default: op
|
||||
headdb.update:
|
||||
default: op
|
||||
headdb.reload:
|
||||
headdb.category.*:
|
||||
default: op
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren