Added `/rules` and `/faq`.

master
James T. Martin 2020-11-22 19:27:23 -08:00
parent 7024ba2e3a
commit 9b10220d74
Signed by: james
GPG Key ID: 4B7F3DA9351E577C
12 changed files with 432 additions and 15 deletions

View File

@ -35,7 +35,8 @@ public class Wasteland extends JavaPlugin {
} else {
towny = new TownyDisabled();
}
saveDefaultConfig();
initializeConfig();
register();
}
@ -50,6 +51,7 @@ public class Wasteland extends JavaPlugin {
public void reload() {
getLogger().info("Reloading wasteland...");
saveDefaultConfig();
reloadConfig();
initializeConfig();
@ -59,7 +61,6 @@ public class Wasteland extends JavaPlugin {
}
private void initializeConfig() {
saveDefaultConfig();
config = ConfigParser.parseConfig(getConfig());
}

View File

@ -3,6 +3,7 @@ package me.jamestmartin.wasteland;
import me.jamestmartin.wasteland.chat.ChatConfig;
import me.jamestmartin.wasteland.kills.KillsConfig;
import me.jamestmartin.wasteland.kit.KitConfig;
import me.jamestmartin.wasteland.manual.ManualConfig;
import me.jamestmartin.wasteland.ranks.AllRanks;
import me.jamestmartin.wasteland.spawns.SpawnsConfig;
@ -13,6 +14,7 @@ public class WastelandConfig {
private final AllRanks ranks;
private final SpawnsConfig spawnsConfig;
private final KitConfig kitConfig;
private final ManualConfig manualConfig;
public WastelandConfig(
String databaseFilename,
@ -20,13 +22,15 @@ public class WastelandConfig {
KillsConfig killsConfig,
AllRanks ranks,
SpawnsConfig spawnsConfig,
KitConfig kitConfig) {
KitConfig kitConfig,
ManualConfig manualConfig) {
this.databaseFilename = databaseFilename;
this.chatConfig = chatConfig;
this.killsConfig = killsConfig;
this.ranks = ranks;
this.spawnsConfig = spawnsConfig;
this.kitConfig = kitConfig;
this.manualConfig = manualConfig;
}
public String getDatabaseFilename() {
@ -52,4 +56,8 @@ public class WastelandConfig {
public KitConfig getKitConfig() {
return kitConfig;
}
public ManualConfig getManualConfig() {
return manualConfig;
}
}

View File

@ -8,6 +8,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import me.jamestmartin.wasteland.chat.ChatState;
import me.jamestmartin.wasteland.kills.KillsState;
import me.jamestmartin.wasteland.kit.KitState;
import me.jamestmartin.wasteland.manual.ManualState;
import me.jamestmartin.wasteland.ranks.PermissionsPlayerRankProvider;
import me.jamestmartin.wasteland.ranks.PlayerRankProvider;
import me.jamestmartin.wasteland.ranks.RanksState;
@ -24,6 +25,7 @@ public class WastelandState implements Substate {
private final RanksState ranksState;
private final SpawnsState spawnsState;
private final KitState kitState;
private final ManualState manualState;
public WastelandState(WastelandConfig config) throws IOException, ClassNotFoundException, SQLException {
this.commandWasteland = new CommandWasteland();
@ -35,10 +37,11 @@ public class WastelandState implements Substate {
this.killsState = new KillsState(config.getKillsConfig(), ranksState.getPlayerKillsStore(), rankProvider);
this.spawnsState = new SpawnsState(config.getSpawnsConfig());
this.kitState = new KitState(config.getKitConfig(), storeState.getKitStore());
this.manualState = new ManualState(config.getManualConfig());
}
private Substate[] getSubstates() {
Substate[] substates = { storeState, chatState, killsState, ranksState, spawnsState, kitState };
Substate[] substates = { storeState, chatState, killsState, ranksState, spawnsState, kitState, manualState };
return substates;
}

View File

@ -9,6 +9,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@ -25,6 +26,8 @@ import me.jamestmartin.wasteland.WastelandConfig;
import me.jamestmartin.wasteland.chat.ChatConfig;
import me.jamestmartin.wasteland.kills.KillsConfig;
import me.jamestmartin.wasteland.kit.KitConfig;
import me.jamestmartin.wasteland.manual.ManualConfig;
import me.jamestmartin.wasteland.manual.ManualSection;
import me.jamestmartin.wasteland.ranks.AllRanks;
import me.jamestmartin.wasteland.ranks.EnlistedRank;
import me.jamestmartin.wasteland.ranks.EnlistedRanks;
@ -48,7 +51,22 @@ public class ConfigParser {
ConfigurationSection officerSection = c.getConfigurationSection("officer");
AllRanks ranks = parseRanks(enlistedSection, officerSection);
return new WastelandConfig(databaseFilename, chatConfig, killsConfig, ranks, spawnsConfig, kitConfig);
ManualSection rules = new ManualSection(
"The Server Rules",
parseManualSectionList(castToSectionList(c.getMapList("rules"))));
ManualSection faq = new ManualSection(
"Frequently Asked Questions",
parseManualSectionList(castToSectionList(c.getMapList("faq"))));
ManualConfig manualConfig = new ManualConfig(rules, faq);
return new WastelandConfig(
databaseFilename,
chatConfig,
killsConfig,
ranks,
spawnsConfig,
kitConfig,
manualConfig);
}
private static ChatConfig parseChatConfig(ConfigurationSection c) {
@ -303,6 +321,34 @@ public class ConfigParser {
return new KitConfig(kitPeriod, kitTools, kitItems);
}
public static ManualSection parseManualSection(Map<?, ?> c) {
String summary = (String) c.get("summary");
Optional<String> details = Optional.ofNullable((String) c.get("details"));
List<ManualSection> subsections = parseManualSectionList(castToSectionList(c.get("sections")));
return new ManualSection(summary, details, subsections);
}
private static List<Map<?, ?>> castToSectionList(Object x) {
if (x == null) {
return null;
}
return ((List<?>) x).stream().map(xx -> (Map<?, ?>) xx).collect(Collectors.toUnmodifiableList());
}
public static List<ManualSection> parseManualSectionList(List<Map<?, ?>> list) {
List<ManualSection> sections = new ArrayList<>();
if (list == null) {
return sections;
}
for (Map<?, ?> section : list) {
sections.add(parseManualSection(section));
}
return sections;
}
/** Orphaned method. */
private static Optional<ChatColor> readColor(ConfigurationSection c, String path) {
return (Optional<ChatColor>) Optional.ofNullable(c.getString(path)).map(ChatColor::valueOf);

View File

@ -30,8 +30,8 @@ public class CommandKit implements CommandExecutor {
}
private Optional<ItemStack> makeTool(Material material, double quality) {
int durability = material.getMaxDurability() - (short) Math.floor(material.getMaxDurability() * rand.nextDouble() * quality);
if (durability <= material.getMaxDurability() / 8) {
int durability = material.getMaxDurability() - (int) (material.getMaxDurability() * rand.nextDouble() / quality);
if (durability <= material.getMaxDurability() / 15) {
return Optional.empty();
}
@ -45,7 +45,6 @@ public class CommandKit implements CommandExecutor {
private Collection<ItemStack> selectKitItems(Player player) throws Exception {
int totalKits = store.getTotalKitsRecieved(player);
player.sendMessage("Total: " + totalKits);
double quality = Math.pow(2, ((double) -totalKits) / 2 + 1);
List<ItemStack> items = new ArrayList<>();
@ -55,10 +54,10 @@ public class CommandKit implements CommandExecutor {
}
for (Entry<Material, Integer> item : config.getKitItems().entrySet()) {
int quantity = (int) Math.ceil(item.getValue() * rand.nextDouble() * quality);
int quantity = (int) (item.getValue() * rand.nextDouble() * quality);
System.out.println(item.getKey().toString());
System.out.println(quantity);
if (quantity > item.getValue() / 8) {
if (quantity > item.getValue() / 15) {
items.add(new ItemStack(item.getKey(), quantity));
}
}
@ -86,8 +85,6 @@ public class CommandKit implements CommandExecutor {
sender.sendMessage("You've already received a kit recently.");
return true;
}
sender.sendMessage("Last time: " + store.getLastKitTime(player));
} catch (Exception e) {
Wasteland.getInstance().getLogger().log(Level.SEVERE, "Failed to get player's last kit time.", e);
sender.sendMessage("ERROR: Failed to get last kit time. Please notify a server administrator.");

View File

@ -0,0 +1,141 @@
package me.jamestmartin.wasteland.manual;
import java.util.Optional;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class CommandManual implements CommandExecutor {
private final String manualName;
private final ManualSection manual;
public CommandManual(String manualName, ManualSection manual) {
this.manualName = manualName;
this.manual = manual;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length > 2) {
sender.sendMessage("Too many arguments.");
return false;
}
final String sectionPath;
final CommandSender target;
if (args.length == 2) {
if (!ManualSection.isSectionPath(args[0])) {
sender.sendMessage("Not a valid section identifier: " + args[0]);
return false;
}
sectionPath = args[0];
target = sender.getServer().getPlayer(args[1]);
if (target == null) {
sender.sendMessage("No such player: " + args[1]);
return false;
}
} else if (args.length == 1) {
if (ManualSection.isSectionPath(args[0])) {
sectionPath = args[0];
target = sender;
} else {
sectionPath = "";
target = sender.getServer().getPlayer(args[0]);
if (target == null) {
sender.sendMessage("No such player: " + args[0]);
return false;
}
}
} else {
sectionPath = "";
target = sender;
}
if (target != sender && !sender.hasPermission("wasteland.manual." + manualName + ".show-other")) {
sender.sendMessage("You do not have permission to forcibly show other players the " + manual.getSummary() + ".");
return true;
}
if (target != sender && !target.hasPermission("wasteland.manual." + manualName)) {
sender.sendMessage("You cannot show someone a manual they do not have permission to view!");
return true;
}
final Optional<ManualSection> section = manual.getSection(sectionPath);
if (section.isEmpty()) {
sender.sendMessage(manual.getSummary() + " does not contain section " + sectionPath + ".");
return false;
}
if (target != sender) {
target.sendMessage("A moderator would like you to review this section of " + manual.getSummary() + ":");
}
if (sectionPath.isEmpty()) {
sendManual(target, section.get());
} else {
target.sendMessage(manual.getSummary() + " section " + sectionPath + ":");
sendManual(target, section.get(), Optional.of(1), 1);
}
if (!section.get().getSections().isEmpty()) {
String pathPrefix = sectionPath.isEmpty() ? "" : sectionPath + ".";
target.sendMessage("For more detail, see `/" + command.getName() + " " + pathPrefix + "#`.");
}
if (target != sender) {
if (!sectionPath.isEmpty()) {
sender.sendMessage(target.getName() + " has been shown " + manualName + " section " + sectionPath + ".");
}
sender.sendMessage(target.getName() + " has been shown " + manualName + ".");
}
return true;
}
private static void sendManual(CommandSender target, ManualSection section, Optional<Integer> maxDepth, int currentIndentation, Optional<Integer> sectionNo) {
StringBuilder indentBuilder = new StringBuilder();
for (int i = 0; i < currentIndentation; i++) {
indentBuilder.append(" ");
}
String indent = indentBuilder.toString();
String header = section.getSummary();
if (sectionNo.isPresent()) {
header = sectionNo.get() + ". " + header;
}
header = indent + header;
if (section.getDetails().isPresent() && maxDepth.map(d -> d > 0).orElse(true)) {
header += " " + section.getDetails().get();
}
target.sendMessage(header);
if (maxDepth.map(d -> d > 0).orElse(true)) {
for (int i = 0; i < section.getSections().size(); i++) {
sendManual(target, section.getSections().get(i), maxDepth.map(d -> d - 1), currentIndentation + 1, i + 1);
}
}
}
private static void sendManual(CommandSender target, ManualSection section, Optional<Integer> maxDepth, int currentIndentation, int sectionNo) {
sendManual(target, section, maxDepth, currentIndentation, Optional.of(sectionNo));
}
private static void sendManual(CommandSender target, ManualSection section, Optional<Integer> maxDepth, int currentIndentation) {
sendManual(target, section, maxDepth, currentIndentation, Optional.empty());
}
private static void sendManual(CommandSender target, ManualSection section, Optional<Integer> maxDepth) {
sendManual(target, section, maxDepth, 0);
}
private static void sendManual(CommandSender target, ManualSection section, int maxDepth) {
sendManual(target, section, Optional.of(maxDepth));
}
private static void sendManual(CommandSender target, ManualSection section) {
sendManual(target, section, 1);
}
}

View File

@ -0,0 +1,19 @@
package me.jamestmartin.wasteland.manual;
public class ManualConfig {
private final ManualSection rules;
private final ManualSection faq;
public ManualConfig(ManualSection rules, ManualSection faq) {
this.rules = rules;
this.faq = faq;
}
public ManualSection getRules() {
return rules;
}
public ManualSection getFaq() {
return faq;
}
}

View File

@ -0,0 +1,89 @@
package me.jamestmartin.wasteland.manual;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ManualSection {
private static final String MANUAL_SECTION_REGEX = "\\d+(\\.\\d+)*";
private final String summary;
private final Optional<String> details;
private final List<ManualSection> sections;
public ManualSection(String summary, Optional<String> details, List<ManualSection> sections) {
this.summary = summary;
this.details = details;
this.sections = sections;
}
public ManualSection(String summary, Optional<String> details) {
this(summary, details, List.of());
}
public ManualSection(String summary, List<ManualSection> subsections) {
this(summary, Optional.empty(), subsections);
}
public ManualSection(String summary) {
this(summary, Optional.empty());
}
public String getSummary() {
return summary;
}
public Optional<String> getDetails() {
return details;
}
public List<ManualSection> getSections() {
return sections;
}
public Optional<ManualSection> getSection(int... path) {
if (path.length == 0) {
return Optional.of(this);
}
return getSection(getSections(), path);
}
private static final int[] parsePath(String path) {
if (path.isEmpty()) {
return new int[0];
}
String[] pathParts = path.split("\\.");
return Stream.of(pathParts).mapToInt(Integer::parseInt).toArray();
}
public Optional<ManualSection> getSection(String path) {
return getSection(parsePath(path));
}
public static Optional<ManualSection> getSection(List<ManualSection> sections, int... path) {
if (path.length == 0) {
throw new IllegalArgumentException("Empty manual section path");
}
if (sections.size() <= path[0] - 1) {
return Optional.empty();
}
int[] rest = IntStream.of(path).skip(1).toArray();
return sections.get(path[0] - 1).getSection(rest);
}
public static Optional<ManualSection> getSection(List<ManualSection> sections, String path) {
if (path.isEmpty()) {
throw new IllegalArgumentException("Empty manual section path");
}
return getSection(sections, parsePath(path));
}
public static final boolean isSectionPath(String path) {
return path.matches(MANUAL_SECTION_REGEX);
}
}

View File

@ -0,0 +1,27 @@
package me.jamestmartin.wasteland.manual;
import org.bukkit.plugin.java.JavaPlugin;
import me.jamestmartin.wasteland.Substate;
public class ManualState implements Substate {
private final CommandManual commandRules;
private final CommandManual commandFaq;
public ManualState(ManualConfig config) {
this.commandRules = new CommandManual("rules", config.getRules());
this.commandFaq = new CommandManual("faq", config.getFaq());
}
@Override
public void register(JavaPlugin plugin) {
plugin.getCommand("rules").setExecutor(commandRules);
plugin.getCommand("faq").setExecutor(commandFaq);
}
@Override
public void unregister(JavaPlugin plugin) {
plugin.getCommand("rules").setExecutor(null);
plugin.getCommand("faq").setExecutor(null);
}
}

View File

@ -1,8 +1,8 @@
package me.jamestmartin.wasteland.ranks;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.entity.Player;
@ -15,7 +15,7 @@ public class RankAttachments {
private final EnlistedRanks ranks;
private final KillsStore killsStore;
private final Map<UUID, PermissionAttachment> attachments = new HashMap<>();
private final Map<UUID, PermissionAttachment> attachments = new ConcurrentHashMap<>();
public RankAttachments(EnlistedRanks ranks, KillsStore killsStore) {
this.ranks = ranks;

View File

@ -366,4 +366,50 @@ kit:
# The item type will not be dropped if the quantity chosen is less than one eighth of the maximum.
items:
ROTTEN_FLESH: 32
rules:
- summary: Be civil and respectful.
sections:
- summary: If you start getting angry or frustrated, take a break!
- summary: Do not harass other players.
- summary: Do not use slurs.
- summary: Do not advertise.
- summary: Do not spam.
- summary: Avoid excessive swearing.
details: 'What is "excessive" is determined by moderator discretion.'
- summary: Be independent and mature.
sections:
- summary: This is a server intended for adults.
details: You will not be banned for being <16 per se, but acting like a child will get you banned no matter how old you are.
- summary: RTFM (Read The Field Manual).
details: Try to answer questions for yourself (e.g. with `/faq`) before asking them.
- summary: Do not whine or beg.
- summary: Do not ask moderators to cheat.
details: They don't have the ability to, anyway.
- summary: Play fairly.
sections:
- summary: Do not use hacks.
details: Hacks are the use of a client or mod designed for cheating.
- summary: Do not use exploits.
details: Exploits are the use of bugs to gain an unfair advantage (e.g. item duplication) or e.g. bypass protection plugins.
- summary: Do not use cheats.
details: Do not use x-ray resource packs or any other form of cheating.
- summary: Use your power appropriately.
details: Use moderator tools and ranks strictly for their intended purpose.
- summary: Only use authorized mods.
details: Refer to the authorized mods list to see which mods are permitted. If you'd like to use a mod not on that list, you **must ask the administrator(s)** before using it.
- summary: Stick to the game.
sections:
- summary: Keep the chat SFW.
- summary: Avoid discussing politics, religion, and the news.
details: This is not strictly forbidden, but these discussions may be shut down by moderator discretion.
- summary: Do not ask for personal information.
details: This includes real name, age, and location.
- summary: Do not share anyone else's personal information.
details: This includes their real name, age, and location.
- summary: Avoid sharing your own personal information.
details: You may share your own real name, age, or location, but not e.g. your address or phone number.
faq:
- summary: Nothing to see here.
details: The server administrator has not set an FAQ.

View File

@ -56,6 +56,17 @@ commands:
usage: "Usage: /<command>"
permission: wasteland.kit
permission-message: You do not have permission to receive a starter kit.
rules:
description: "Read the server rules. You can view more information about a specific rule using `/rules #`."
usage: "Usage: /<command> [<section>] [<player>]"
permission: wasteland.manual.rules
permission-message: You do not have permission to read the server rules.
faq:
description: "Read the server FAQ. You can view more information about a specific topic using `/faq #`."
usage: "Usage: /<command> [<section>] [<player>]"
permission: wasteland.manual.faq
permission-message: You do not have permission to read the server FAQ.
permissions:
wasteland.reload:
@ -117,3 +128,32 @@ permissions:
wasteland.kit:
description: Allows you to receive a starter kit.
default: true
wasteland.manual:
description: Allows you to read any manual.
default: op
children:
wasteland.manual.rules: true
wasteland.manual.faq: true
wasteland.manual.show-other:
description: Allows you to forcibly show any manual to someone else.
default: op
children:
wasteland.manual.rules.show-other: true
wasteland.manual.faq.show-other: true
wasteland.manual.rules:
description: Allows you to read the server rules.
default: true
wasteland.manual.rules.show-other:
description: Allows you to forcibly show the server rules to someone else.
default: op
children:
wasteland.manual.rules: true
wasteland.manual.faq:
description: Allows you to read the FAQ.
default: true
wasteland.manual.faq.show-other:
description: Allows you to forcibly show the FAQ to someone else.
default: op
children:
wasteland.manual.faq: true