Start implementation of cooldown syncing

Add message parameter {TimeLeft} to command cooldown message
Fix grammar of comment
Move command handling in it's own package, preparation for the rewrite of the command handling
This commit is contained in:
GeorgH93 2017-01-24 20:16:21 +01:00
parent ba5eb84c61
commit 41717e2f09
13 changed files with 129 additions and 79 deletions

View File

@ -8,13 +8,15 @@ Language: en
# Update = the old language file will be updated with the new english messages, all your changes to the file will survive the update
LanguageUpdateMode: Overwrite
# Title to be showen for the opened inventory for everyone except the owner of the backpack. Can contain {OwnerName} (which will be replaced with the players name).
# Title to be shown for the opened inventory for everyone except the owner of the backpack. Can contain {OwnerName} (which will be replaced with the players name).
BackpackTitleOther: "&b{OwnerName}'s Backpack"
# The title of the inventory for the owner of the backpack.
BackpackTitle: "&bBackpack"
# Defines how long a player have to wait till he can reopen his backpack.
# Time is in seconds. Values < 1 disable the cooldown.
command_cooldown: -1
# If enabled whe cooldown will be synced between servers. It will also prevent players from leaving and joining to bypass the cooldown.
sync_cooldown: false
# Defines if the content of the backpack get droped on the death of a player.
# If enabled, it can be disabled for individual players with the "backpack.KeepOnDeath" permission.
drop_on_death: true
@ -67,6 +69,7 @@ Database:
# Don't change the players table if you have backpacks stored in your database already! Player id's might wont match anymore resulting data inconsitency.
User: backpack_players
Backpack: backpacks
Cooldown: backpack_cooldowns
# Field settings for the tables
# Do not change them after the tables have been generated!
# If you like to change them after the tables have been generated alter the tables manualy or delete them (the system then will regenerate them).

View File

@ -7,7 +7,8 @@ Language:
PlayerBackpackClose: "{OwnerName}'s Rucksack geschlossen."
InvalidBackpack: "Rucksack fehlerhaft."
BackpackCleaned: "Rucksack gelehrt."
Cooldown: "&2Bitte warte kurz bis du deinen Rucksack wieder öffnest."
#Parameter> {TimeLeft} time in seconds till he can reopen his backpack
Cooldown: "&2Bitte warte noch {TimeLeft} Sekunden bis du deinen Rucksack wieder öffnest."
Shulkerboxes:
NotAllowedInBackpack: "&cShulkerboxes sind im Rucksack nicht erlaubt."
Commands:

View File

@ -7,7 +7,8 @@ Language:
PlayerBackpackClose: "{OwnerName}'s backpack closed!"
InvalidBackpack: "Invalid backpack."
BackpackCleaned: "Backpack cleaned."
Cooldown: "&2Please wait till you reopen your backpack."
#Parameter> {TimeLeft} time in seconds till he can reopen his backpack
Cooldown: "&2Please wait {TimeLeft} seconds till you reopen your backpack."
Shulkerboxes:
NotAllowedInBackpack: "&cShulkerboxes are not allowed in the backpack."
Commands:

View File

@ -1,7 +1,7 @@
name: ${project.name}
author: ${author}
website: ${website}
main: at.pcgamingfreaks.Minepacks.Bukkit.Minepacks
main: ${project.groupId}.${project.artifactId}.Bukkit.${project.artifactId}
description: ${project.description}
version: ${project.version}
depend: [PCGF_MC_Plugin_Lib]

View File

@ -20,13 +20,6 @@
import at.pcgamingfreaks.Bukkit.NMSReflection;
import at.pcgamingfreaks.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
@ -34,6 +27,13 @@
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
public class Backpack implements at.pcgamingfreaks.Minepacks.Bukkit.API.Backpack
{
private final static Method METHOD_GET_INVENTORY = NMSReflection.getOBCMethod("inventory.CraftInventory", "getInventory");
@ -88,14 +88,14 @@ public void setOwnerID(int id)
}
@Override
public void open(@NotNull Player p, boolean editable)
public void open(@NotNull Player player, boolean editable)
{
if(owner.isOnline())
{
Player player = owner.getPlayer();
if(player != null)
Player owner = this.owner.getPlayer();
if(owner != null)
{
int size = Minepacks.getInstance().getBackpackPermSize(player);
int size = Minepacks.getInstance().getBackpackPermSize(owner);
if(size != bp.getSize())
{
List<ItemStack> items = setSize(size);
@ -103,13 +103,13 @@ public void open(@NotNull Player p, boolean editable)
{
if (i != null)
{
player.getWorld().dropItemNaturally(player.getLocation(), i);
owner.getWorld().dropItemNaturally(owner.getLocation(), i);
}
}
}
}
}
opened.put(p, editable);
opened.put(player, editable);
// It's not perfect, but it is the only way of doing this.
// This sets the title of the inventory based on the person who is opening it.
@ -118,14 +118,14 @@ public void open(@NotNull Player p, boolean editable)
try
{
FIELD_TITLE.setAccessible(true);
FIELD_TITLE.set(METHOD_GET_INVENTORY.invoke(bp), p.equals(owner) ? Minepacks.getInstance().backpackTitle : titleOther);
FIELD_TITLE.set(METHOD_GET_INVENTORY.invoke(bp), player.equals(owner) ? Minepacks.getInstance().backpackTitle : titleOther);
}
catch(Exception e)
{
e.printStackTrace();
}
p.openInventory(bp);
player.openInventory(bp);
}
public void close(Player p)

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 GeorgH93
* Copyright (C) 2017 GeorgH93
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -15,10 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package at.pcgamingfreaks.Minepacks.Bukkit;
package at.pcgamingfreaks.Minepacks.Bukkit.Commands;
import at.pcgamingfreaks.Bukkit.Message.Message;
import at.pcgamingfreaks.Minepacks.Bukkit.API.Callback;
import at.pcgamingfreaks.Minepacks.Bukkit.Backpack;
import at.pcgamingfreaks.Minepacks.Bukkit.Minepacks;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@ -28,23 +30,21 @@
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Date;
public class OnCommand implements CommandExecutor
{
private Minepacks plugin;
public Message messageNotFromConsole, messageBackpackCleaned, messageCooldown;
public final int cooldown;
private final Minepacks plugin;
private final Message messageNotFromConsole, messageBackpackCleaned, messageCooldown;
private final long cooldown;
private final boolean syncCooldown;
public OnCommand(Minepacks mp)
{
plugin = mp;
messageNotFromConsole = plugin.lang.getMessage("NotFromConsole");
messageBackpackCleaned = plugin.lang.getMessage("Ingame.BackpackCleaned");
messageCooldown = plugin.lang.getMessage("Ingame.Cooldown");
messageCooldown = plugin.lang.getMessage("Ingame.Cooldown").replaceAll("\\{TimeLeft}", "%1$.1f");
cooldown = plugin.config.getCommandCooldown();
syncCooldown = plugin.config.isCommandCooldownSyncEnabled();
}
@SuppressWarnings("deprecation")
@ -70,13 +70,20 @@ public boolean onCommand(CommandSender sender, Command cmd, String arg, String[]
{
if(plugin.cooldowns.containsKey(player))
{
if(((new Date()).getTime() - plugin.cooldowns.get(player)) < cooldown)
long cd = plugin.cooldowns.get(player);
if(cd < System.currentTimeMillis())
{
messageCooldown.send(sender);
cd = cd - System.currentTimeMillis();
messageCooldown.send(sender, cd / 1000f);
return true;
}
}
plugin.cooldowns.put(player, (new Date()).getTime());
final long cooldownTime = System.currentTimeMillis() + cooldown;
if(syncCooldown)
{
plugin.getDb().syncCooldown(player, cooldownTime);
}
plugin.cooldowns.put(player, cooldownTime);
}
plugin.openBackpack(player, player, true);
}

View File

@ -122,6 +122,11 @@ public String getBackpackTable()
return config.getString("Database.Tables.Backpack", "backpacks");
}
public String getCooldownTable()
{
return config.getString("Database.Tables.Cooldown", "backpack_cooldowns");
}
public String getDBFields(String sub)
{
return config.getString("Database.Tables.Fields." + sub, "");
@ -183,11 +188,16 @@ public boolean getAutoUpdate()
return config.getBoolean("auto-update", true);
}
public int getCommandCooldown()
public long getCommandCooldown()
{
return config.getInt("command_cooldown", -1) * 1000;
}
public boolean isCommandCooldownSyncEnabled()
{
return config.getBoolean("sync_cooldown", false);
}
//region Full inventory handling
public boolean getFullInvCollect()
{

View File

@ -35,7 +35,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Database implements Listener
public abstract class Database implements Listener
{
protected static final String START_UUID_UPDATE = "Start updating database to UUIDs ...", UUIDS_UPDATED = "Updated %d accounts to UUIDs.";
@ -207,11 +207,13 @@ public void updatePlayerAndLoadBackpack(Player player)
asyncLoadBackpack(player);
}
public void updatePlayer(Player player) {}
public abstract void updatePlayer(Player player);
public void saveBackpack(Backpack backpack) {}
public abstract void saveBackpack(Backpack backpack);
protected Backpack loadBackpack(OfflinePlayer player) { return null; }
public abstract void syncCooldown(Player player, long time);
protected abstract Backpack loadBackpack(OfflinePlayer player);
protected void loadBackpack(final OfflinePlayer player, final Callback<Backpack> callback)
{

View File

@ -28,6 +28,7 @@
public class MySQL extends SQL
{
//TODO add cooldown sync table
public MySQL(Minepacks mp)
{
super(mp); // Load Settings

View File

@ -30,26 +30,31 @@
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.sql.*;
import java.util.*;
public abstract class SQL extends Database
{
//TODO test sync cooldown
private HikariDataSource dataSource;
protected String tablePlayers, tableBackpacks; // Table Names
protected String fieldPlayerName, fieldPlayerID, fieldPlayerUUID, fieldBpOwner, fieldBpIts, fieldBpVersion, fieldBpLastUpdate; // Table Fields
protected String queryUpdatePlayerAdd, queryGetPlayerID, queryInsertBp, queryUpdateBp, queryGetBP, queryDeleteOldBackpacks, queryGetUnsetOrInvalidUUIDs, queryFixUUIDs; // DB Querys
protected boolean updatePlayer;
protected String tablePlayers, tableBackpacks, tableCooldowns; // Table Names
protected String fieldPlayerName, fieldPlayerID, fieldPlayerUUID, fieldBpOwner, fieldBpIts, fieldBpVersion, fieldBpLastUpdate, fieldCdPlayer, fieldCdTime; // Table Fields
protected String queryUpdatePlayerAdd, queryGetPlayerID, queryInsertBp, queryUpdateBp, queryGetBP, queryDeleteOldBackpacks, queryGetUnsetOrInvalidUUIDs, queryFixUUIDs, querySyncCooldown; // DB Querys
protected boolean updatePlayer, syncCooldown;
public SQL(Minepacks mp)
{
super(mp);
HikariConfig poolConfig = getPoolConfig();
poolConfig.setPoolName("Minepacks-Connection-Pool");
dataSource = new HikariDataSource(poolConfig);
if(poolConfig != null)
{
poolConfig.setPoolName("Minepacks-Connection-Pool");
dataSource = new HikariDataSource(poolConfig);
}
loadSettings();
buildQuerys();
@ -76,21 +81,25 @@ public SQL(Minepacks mp)
}
}
protected abstract HikariConfig getPoolConfig();
protected abstract @Nullable HikariConfig getPoolConfig();
protected void loadSettings()
{
// Load table and field names
tablePlayers = plugin.config.getUserTable();
tableBackpacks = plugin.config.getBackpackTable();
fieldPlayerID = plugin.config.getDBFields("User.Player_ID");
fieldPlayerName = plugin.config.getDBFields("User.Name");
fieldPlayerUUID = plugin.config.getDBFields("User.UUID");
fieldBpOwner = plugin.config.getDBFields("Backpack.Owner_ID");
fieldBpIts = plugin.config.getDBFields("Backpack.ItemStacks");
fieldBpVersion = plugin.config.getDBFields("Backpack.Version");
tablePlayers = plugin.config.getUserTable();
tableBackpacks = plugin.config.getBackpackTable();
tableCooldowns = plugin.config.getCooldownTable();
fieldPlayerID = plugin.config.getDBFields("User.Player_ID");
fieldPlayerName = plugin.config.getDBFields("User.Name");
fieldPlayerUUID = plugin.config.getDBFields("User.UUID");
fieldBpOwner = plugin.config.getDBFields("Backpack.Owner_ID");
fieldBpIts = plugin.config.getDBFields("Backpack.ItemStacks");
fieldBpVersion = plugin.config.getDBFields("Backpack.Version");
fieldBpLastUpdate = plugin.config.getDBFields("Backpack.LastUpdate");
updatePlayer = plugin.config.getUpdatePlayer();
fieldCdPlayer = plugin.config.getDBFields("Cooldown.Player_ID");
fieldCdTime = plugin.config.getDBFields("Cooldown.Time");
updatePlayer = plugin.config.getUpdatePlayer();
syncCooldown = plugin.config.isCommandCooldownSyncEnabled();
}
@Override
@ -178,36 +187,38 @@ protected Connection getConnection() throws SQLException
protected final void buildQuerys()
{
// Build the SQL querys with placeholders for the table and field names
queryGetBP = "SELECT `{FieldBPOwner}`,`{FieldBPITS}`,`{FieldBPVersion}` FROM `{TableBackpacks}` INNER JOIN `{TablePlayers}` ON `{TableBackpacks}`.`{FieldBPOwner}`=`{TablePlayers}`.`{FieldPlayerID}` WHERE ";
queryGetBP = "SELECT {FieldBPOwner},{FieldBPITS},{FieldBPVersion} FROM {TableBackpacks} INNER JOIN {TablePlayers} ON {TableBackpacks}.{FieldBPOwner}={TablePlayers}.{FieldPlayerID} WHERE ";
if(useUUIDs)
{
queryUpdatePlayerAdd = "INSERT INTO `{TablePlayers}` (`{FieldName}`,`{FieldUUID}`) VALUES (?,?) ON DUPLICATE KEY UPDATE `{FieldName}`=?;";
queryGetPlayerID = "SELECT `{FieldPlayerID}` FROM `{TablePlayers}` WHERE `{FieldUUID}`=?;";
queryGetBP += "`{FieldUUID}`=?;";
queryUpdatePlayerAdd = "INSERT INTO {TablePlayers} ({FieldName},{FieldUUID}) VALUES (?,?) ON DUPLICATE KEY UPDATE {FieldName}=?;";
queryGetPlayerID = "SELECT {FieldPlayerID} FROM {TablePlayers} WHERE {FieldUUID}=?;";
queryGetBP += "{FieldUUID}=?;";
querySyncCooldown = "INSERT INTO {TableCooldowns} ({FieldCDPlayer},{FieldCDTime}) SELECT {FieldPlayerID},? FROM {TablePlayers} WHERE {FieldUUID}=?;";
}
else
{
queryUpdatePlayerAdd = "INSERT IGNORE INTO `{TablePlayers}` (`{FieldName}`) VALUES (?);";
queryGetPlayerID = "SELECT `{FieldPlayerID}` FROM `{TablePlayers}` WHERE `{FieldName}`=?;";
queryGetBP += "`{FieldName}`=?;";
queryUpdatePlayerAdd = "INSERT IGNORE INTO {TablePlayers} ({FieldName}) VALUES (?);";
queryGetPlayerID = "SELECT {FieldPlayerID} FROM {TablePlayers} WHERE {FieldName}=?;";
queryGetBP += "{FieldName}=?;";
querySyncCooldown = "INSERT INTO {TableCooldowns} ({FieldCDPlayer},{FieldCDTime}) SELECT {FieldPlayerID},? FROM {TablePlayers} WHERE {FieldName}=?;";
}
queryInsertBp = "INSERT INTO `{TableBackpacks}` (`{FieldBPOwner}`,`{FieldBPITS}`,`{FieldBPVersion}`) VALUES (?,?,?);";
queryUpdateBp = "UPDATE `{TableBackpacks}` SET `{FieldBPITS}`=?,`{FieldBPVersion}`=?";
queryInsertBp = "INSERT INTO {TableBackpacks} ({FieldBPOwner},{FieldBPITS},{FieldBPVersion}) VALUES (?,?,?);";
queryUpdateBp = "UPDATE {TableBackpacks} SET {FieldBPITS}=?,{FieldBPVersion}=?";
if(maxAge > 0)
{
queryUpdateBp += ",`{FieldBPLastUpdate}`={NOW}";
queryUpdateBp += ",{FieldBPLastUpdate}={NOW}";
}
queryUpdateBp += " WHERE `{FieldBPOwner}`=?;";
queryDeleteOldBackpacks = "DELETE FROM `{TableBackpacks}` WHERE `{FieldBPLastUpdate}` < DATE('now', '-{VarMaxAge} days')";
queryUpdateBp += " WHERE {FieldBPOwner}=?;";
queryDeleteOldBackpacks = "DELETE FROM {TableBackpacks} WHERE {FieldBPLastUpdate} < DATE('now', '-{VarMaxAge} days')";
if(useUUIDSeparators)
{
queryGetUnsetOrInvalidUUIDs = "SELECT `{FieldPlayerID}`,`{FieldName}`,`{FieldUUID}` FROM `{TablePlayers}` WHERE `{FieldUUID}` IS NULL OR `{FieldUUID}` NOT LIKE '%-%-%-%-%';";
queryGetUnsetOrInvalidUUIDs = "SELECT {FieldPlayerID},{FieldName},{FieldUUID} FROM {TablePlayers} WHERE {FieldUUID} IS NULL OR {FieldUUID} NOT LIKE '%-%-%-%-%';";
}
else
{
queryGetUnsetOrInvalidUUIDs = "SELECT `{FieldPlayerID}`,`{FieldName}`,`{FieldUUID}` FROM `{TablePlayers}` WHERE `{FieldUUID}` IS NULL OR `{FieldUUID}` LIKE '%-%';";
queryGetUnsetOrInvalidUUIDs = "SELECT {FieldPlayerID},{FieldName},{FieldUUID} FROM {TablePlayers} WHERE {FieldUUID} IS NULL OR {FieldUUID} LIKE '%-%';";
}
queryFixUUIDs = "UPDATE `{TablePlayers}` SET `{FieldUUID}`=? WHERE `{FieldPlayerID}`=?;";
queryFixUUIDs = "UPDATE {TablePlayers} SET {FieldUUID}=? WHERE {FieldPlayerID}=?;";
updateQuerysForDialect();
@ -225,6 +236,7 @@ protected void setTableAndFieldNames()
queryFixUUIDs = replacePlaceholders(queryFixUUIDs);
queryDeleteOldBackpacks = replacePlaceholders(queryDeleteOldBackpacks.replaceAll("\\{VarMaxAge}", maxAge + ""));
queryGetUnsetOrInvalidUUIDs = replacePlaceholders(queryGetUnsetOrInvalidUUIDs);
querySyncCooldown = replacePlaceholders(querySyncCooldown);
}
protected abstract void updateQuerysForDialect();
@ -234,7 +246,8 @@ protected String replacePlaceholders(String query)
return query.replaceAll("(\\{\\w+})", "`$1`").replaceAll("`(\\{\\w+})`_(\\w+)", "`$1_$2`").replaceAll("fk_`(\\{\\w+})`_`(\\{\\w+})`_`(\\{\\w+})`", "`fk_$1_$2_$3`") // Fix name formatting
.replaceAll("\\{TablePlayers}", tablePlayers).replaceAll("\\{FieldName}", fieldPlayerName).replaceAll("\\{FieldUUID}", fieldPlayerUUID).replaceAll("\\{FieldPlayerID}", fieldPlayerID) // Players
.replaceAll("\\{TableBackpacks}", tableBackpacks).replaceAll("\\{FieldBPOwner}", fieldBpOwner).replaceAll("\\{FieldBPITS}", fieldBpIts) // Backpacks
.replaceAll("\\{FieldBPVersion}", fieldBpVersion).replaceAll("\\{FieldBPLastUpdate}", fieldBpLastUpdate); // Backpacks
.replaceAll("\\{FieldBPVersion}", fieldBpVersion).replaceAll("\\{FieldBPLastUpdate}", fieldBpLastUpdate) // Backpacks
.replaceAll("\\{TableCooldowns}", tableCooldowns).replaceAll("\\{FieldCDPlayer}", fieldCdPlayer).replaceAll("\\{FieldCDTime}", fieldCdTime); // Cooldowns
}
protected void runStatementAsync(final String query, final Object... args)
@ -416,4 +429,10 @@ public Backpack loadBackpack(OfflinePlayer player) // The sync function shouldn'
}
return null;
}
@Override
public void syncCooldown(Player player, long cooldownTime)
{
runStatementAsync(querySyncCooldown, new Timestamp(cooldownTime), getPlayerNameOrUUID(player));
}
}

View File

@ -32,6 +32,7 @@
public class SQLite extends SQL
{
//TODO add cooldown sync table
public SQLite(Minepacks mp)
{
super(mp);
@ -41,17 +42,20 @@ public SQLite(Minepacks mp)
protected void loadSettings()
{
// Set table and field names to fixed values to prevent users from destroying old databases.
fieldPlayerID = "player_id";
fieldPlayerName = "name";
fieldPlayerUUID = "uuid";
fieldBpOwner = "owner";
fieldPlayerID = "player_id";
fieldPlayerName = "name";
fieldPlayerUUID = "uuid";
fieldBpOwner = "owner";
//noinspection SpellCheckingInspection
fieldBpIts = "itemstacks";
fieldBpVersion = "version";
fieldBpIts = "itemstacks";
fieldBpVersion = "version";
//noinspection SpellCheckingInspection
fieldBpLastUpdate = "lastupdate";
tablePlayers = "backpack_players";
tableBackpacks = "backpacks";
tablePlayers = "backpack_players";
tableBackpacks = "backpacks";
tableCooldowns = "backpack_cooldowns";
fieldCdPlayer = "player_id";
fieldCdTime = "time";
// Set fixed settings
useUUIDSeparators = false;
updatePlayer = true;

View File

@ -45,7 +45,7 @@ public void run()
{
if(player.getInventory().firstEmpty() == -1 && player.hasPermission("backpack") && player.hasPermission("backpack.fullpickup"))
{
// Only check loaded backpacks (loading them would take to much time for a repeating task, the will be loaded async soon enough)
// Only check loaded backpacks (loading them would take to much time for a repeating task, the backpack will be loaded async soon enough)
Backpack backpack = plugin.getBackpackCachedOnly(player);
if(backpack == null)
{

View File

@ -24,6 +24,7 @@
import at.pcgamingfreaks.ConsoleColor;
import at.pcgamingfreaks.Minepacks.Bukkit.API.Callback;
import at.pcgamingfreaks.Minepacks.Bukkit.API.MinepacksPlugin;
import at.pcgamingfreaks.Minepacks.Bukkit.Commands.OnCommand;
import at.pcgamingfreaks.Minepacks.Bukkit.Database.Config;
import at.pcgamingfreaks.Minepacks.Bukkit.Database.Language;
import at.pcgamingfreaks.Minepacks.Bukkit.Listener.DisableShulkerboxes;
@ -45,6 +46,7 @@
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class Minepacks extends JavaPlugin implements MinepacksPlugin
{
@ -55,7 +57,7 @@ public class Minepacks extends JavaPlugin implements MinepacksPlugin
public Language lang;
private Database database;
public HashMap<Player, Long> cooldowns = new HashMap<>();
public final Map<Player, Long> cooldowns = new HashMap<>();
public String backpackTitleOther, backpackTitle;
public Message messageNoPermission, messageInvalidBackpack;