Use arena class slugs for class selection.

This commit is a minor refactoring of the class selection functionality
plugin-wide. Instead of selecting classes based on the "lowercase name"
of a class, commands and listeners are now "slug aware", as it were.

The ArenaClass class now uses its slug instead of its "lowercase name"
for equality and hash codes.

The `/ma class` command now tab completes slugs, but it still supports
class names as they appear in the config-file when executing the command
itself. The same applies to the `/ma classchest` command.

The sign handling in ArenaListener slugifies sign text instead of just
lowercasing and stripping spaces.
This commit is contained in:
Andreas Troelsen 2020-11-01 14:01:38 +01:00
parent 519886cf3e
commit 5bcab8fa46
8 changed files with 43 additions and 38 deletions

View File

@ -24,6 +24,7 @@ These changes will (most likely) be included in the next version.
- New command `/ma addreward <player> <thing>` can be used to add a reward to an arena player's reward list. This can be useful for hooking into the rewards system from scripts or other plugins.
- The Root Target ability now uses potion effects (slowness, slow falling, and negative jump boost) instead of repeated teleports. This should make for a smoother root experience.
- Permissions for arenas and classes are now based on "slugs". It is now possible to configure permissions for arenas and classes with multi-word names (including "My Items"). Check the Permissions page on the wiki for details.
- Commands that resolve class names now consistently resolve and tab complete "slugs" instead of arbitrarily "squashed" names. This greatly improves support for multi-word names.
- Using `spectate-on-death: true` no longer forces players out to their join location/exit warp before moving them to the spectator area. This should prevent "jumpy" behavior in multi-world setups.
- Players should now properly respawn at the spectator area rather than at world spawn on servers with plugins that override respawn locations.
- Config-files with missing `pet-items` nodes no longer errors. A missing `pet-items` node in `global-settings` is treated as empty, i.e. no pet items will be registered.

View File

@ -16,7 +16,7 @@ import java.util.stream.IntStream;
public class ArenaClass
{
private String configName, lowercaseName;
private String configName;
private String slug;
private Thing helmet, chestplate, leggings, boots, offhand;
private List<Thing> armor;
@ -35,7 +35,6 @@ public class ArenaClass
public ArenaClass(String name, Thing price, boolean unbreakableWeapons, boolean unbreakableArmor) {
this.configName = name;
this.slug = Slugs.create(name);
this.lowercaseName = name.toLowerCase().replace(" ", "");
this.items = new ArrayList<>();
this.armor = new ArrayList<>(4);
@ -72,7 +71,7 @@ public class ArenaClass
*/
@Deprecated
public String getLowercaseName() {
return lowercaseName;
return slug;
}
/**
@ -238,12 +237,12 @@ public class ArenaClass
if (!this.getClass().equals(o.getClass())) return false;
ArenaClass other = (ArenaClass) o;
return other.lowercaseName.equals(this.lowercaseName);
return other.slug.equals(this.slug);
}
@Override
public int hashCode() {
return lowercaseName.hashCode();
return slug.hashCode();
}
public static class MyItems extends ArenaClass {

View File

@ -757,7 +757,8 @@ public class ArenaImpl implements Arena
if (defaultClass != null) {
// Assign default class if applicable
if (!ClassChests.assignClassFromStoredClassChest(this, p, defaultClass)) {
assignClass(p, defaultClass.getLowercaseName());
String slug = defaultClass.getSlug();
assignClass(p, slug);
messenger.tell(p, Msg.LOBBY_CLASS_PICKED, defaultClass.getConfigName());
}
}
@ -1319,10 +1320,10 @@ public class ArenaImpl implements Arena
}
int index = MobArena.random.nextInt(classes.size());
String className = classes.get(index).getConfigName();
String slug = classes.get(index).getSlug();
assignClass(p, className);
messenger.tell(p, Msg.LOBBY_CLASS_PICKED, this.classes.get(className).getConfigName());
assignClass(p, slug);
messenger.tell(p, Msg.LOBBY_CLASS_PICKED, this.classes.get(slug).getConfigName());
}
private void addClassPermissions(Player player) {

View File

@ -17,6 +17,7 @@ import com.garbagemule.MobArena.things.ExperienceThing;
import com.garbagemule.MobArena.things.Thing;
import com.garbagemule.MobArena.things.ThingPicker;
import com.garbagemule.MobArena.util.ClassChests;
import com.garbagemule.MobArena.util.Slugs;
import com.garbagemule.MobArena.waves.MABoss;
import org.bukkit.ChatColor;
import org.bukkit.Location;
@ -1114,15 +1115,16 @@ public class ArenaListener
private void handleSign(Sign sign, Player p) {
// Check if the first line is a class name.
String className = ChatColor.stripColor(sign.getLine(0)).toLowerCase().replace(" ", "");
String className = ChatColor.stripColor(sign.getLine(0));
String slug = Slugs.create(className);
if (!arena.getClasses().containsKey(className) && !className.equals("random"))
if (!arena.getClasses().containsKey(slug) && !slug.equals("random"))
return;
ArenaClass newAC = arena.getClasses().get(className);
ArenaClass newAC = arena.getClasses().get(slug);
// Check for permission.
if (!newAC.hasPermission(p) && !className.equals("random")) {
if (!newAC.hasPermission(p) && !slug.equals("random")) {
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PERMISSION);
return;
}
@ -1154,7 +1156,7 @@ public class ArenaListener
classLimits.playerPickedClass(newAC, p);
// Delay the inventory stuff to ensure that right-clicking works.
delayAssignClass(p, className, price, sign);
delayAssignClass(p, slug, price, sign);
}
/*private boolean cansPlayerJoinClass(ArenaClass ac, Player p) {
@ -1169,12 +1171,12 @@ public class ArenaListener
return true;
}*/
private void delayAssignClass(final Player p, final String className, final Thing price, final Sign sign) {
private void delayAssignClass(final Player p, final String slug, final Thing price, final Sign sign) {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin,new Runnable() {
public void run() {
if (!className.equalsIgnoreCase("random")) {
if (!slug.equalsIgnoreCase("random")) {
if (useClassChests) {
ArenaClass ac = plugin.getArenaMaster().getClasses().get(className.toLowerCase().replace(" ", ""));
ArenaClass ac = plugin.getArenaMaster().getClasses().get(slug);
if (ClassChests.assignClassFromStoredClassChest(arena, p, ac)) {
return;
}
@ -1183,8 +1185,8 @@ public class ArenaListener
}
// Otherwise just fall through and use the items from the config-file
}
arena.assignClass(p, className);
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(className).getConfigName());
arena.assignClass(p, slug);
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(slug).getConfigName());
if (price != null) {
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PRICE, price.toString());
}

View File

@ -337,14 +337,13 @@ public class ArenaMasterImpl implements ArenaMaster
private ArenaClass loadClass(String classname) {
FileConfiguration config = plugin.getConfig();
ConfigurationSection section = config.getConfigurationSection("classes." + classname);
String lowercase = classname.toLowerCase().replace(" ", "");
// If the section doesn't exist, the class doesn't either.
if (section == null) {
// We may not have a class entry for My Items, but that's fine
if (classname.equals("My Items")) {
ArenaClass myItems = new ArenaClass.MyItems(null, false, false, this);
classes.put(lowercase, myItems);
classes.put(myItems.getSlug(), myItems);
return myItems;
}
plugin.getLogger().severe("Failed to load class '" + classname + "'.");
@ -393,7 +392,7 @@ public class ArenaMasterImpl implements ArenaMaster
}
// Finally add the class to the classes map.
classes.put(lowercase, arenaClass);
classes.put(arenaClass.getSlug(), arenaClass);
return arenaClass;
}

View File

@ -8,6 +8,7 @@ import com.garbagemule.MobArena.commands.Command;
import com.garbagemule.MobArena.commands.CommandInfo;
import com.garbagemule.MobArena.commands.Commands;
import com.garbagemule.MobArena.framework.ArenaMaster;
import com.garbagemule.MobArena.util.Slugs;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
@ -37,7 +38,8 @@ public class ClassChestCommand implements Command {
// Require a class name
if (args.length != 1) return false;
ArenaClass ac = am.getClasses().get(args[0].toLowerCase());
String slug = Slugs.create(args[0]);
ArenaClass ac = am.getClasses().get(slug);
if (ac == null) {
am.getGlobalMessenger().tell(sender, "Class not found.");
return true;
@ -69,13 +71,13 @@ public class ClassChestCommand implements Command {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
String prefix = Slugs.create(args[0]);
Collection<ArenaClass> classes = am.getClasses().values();
return classes.stream()
.filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix))
.map(ArenaClass::getConfigName)
.filter(cls -> cls.getSlug().startsWith(prefix))
.map(ArenaClass::getSlug)
.collect(Collectors.toList());
}
}

View File

@ -10,6 +10,7 @@ import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import com.garbagemule.MobArena.things.Thing;
import com.garbagemule.MobArena.util.ClassChests;
import com.garbagemule.MobArena.util.Slugs;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@ -51,15 +52,15 @@ public class PickClassCommand implements Command
}
// Grab the ArenaClass, if it exists
String lowercase = args[0].toLowerCase();
ArenaClass ac = am.getClasses().get(lowercase);
String slug = Slugs.create(args[0]);
ArenaClass ac = am.getClasses().get(slug);
if (ac == null) {
arena.getMessenger().tell(p, Msg.LOBBY_NO_SUCH_CLASS, lowercase);
arena.getMessenger().tell(p, Msg.LOBBY_NO_SUCH_CLASS, slug);
return true;
}
// Check for permission.
if (!ac.hasPermission(p) && !lowercase.equals("random")) {
if (!ac.hasPermission(p) && !slug.equals("random")) {
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PERMISSION);
return true;
}
@ -88,15 +89,15 @@ public class PickClassCommand implements Command
clm.playerLeftClass(oldAC, p);
clm.playerPickedClass(ac, p);
if (!lowercase.equalsIgnoreCase("random")) {
if (!slug.equalsIgnoreCase("random")) {
if (arena.getSettings().getBoolean("use-class-chests", false)) {
if (ClassChests.assignClassFromStoredClassChest(arena, p, ac)) {
return true;
}
// No linked chest? Fall through to config-file
}
arena.assignClass(p, lowercase);
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(lowercase).getConfigName());
arena.assignClass(p, slug);
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(slug).getConfigName());
if (price != null) {
arena.getMessenger().tell(p, Msg.LOBBY_CLASS_PRICE, price.toString());
}
@ -118,9 +119,9 @@ public class PickClassCommand implements Command
Collection<ArenaClass> classes = am.getClasses().values();
return classes.stream()
.filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix))
.filter(cls -> cls.getSlug().startsWith(prefix))
.filter(cls -> cls.hasPermission(player))
.map(ArenaClass::getConfigName)
.map(ArenaClass::getSlug)
.collect(Collectors.toList());
}
}

View File

@ -96,7 +96,7 @@ public class ClassChests {
}
private static void assignClassAndGrantChestItems(Arena arena, Player player, ArenaClass ac, Block block) {
String classname = ac.getLowercaseName();
String slug = ac.getSlug();
InventoryHolder holder = (InventoryHolder) block.getState();
ItemStack[] contents = holder.getInventory().getContents();
@ -106,8 +106,8 @@ public class ClassChests {
System.arraycopy(contents, 0, newContents, 0, 36);
contents = newContents;
}
arena.assignClassGiveInv(player, classname, contents);
arena.getMessenger().tell(player, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(classname).getConfigName());
arena.assignClassGiveInv(player, slug, contents);
arena.getMessenger().tell(player, Msg.LOBBY_CLASS_PICKED, arena.getClasses().get(slug).getConfigName());
Thing price = ac.getPrice();
if (price != null) {