From a558a9d755f0ca0dafa53db654dd17594a479e58 Mon Sep 17 00:00:00 2001 From: test Date: Thu, 16 May 2024 13:51:54 +1000 Subject: [PATCH] Various Wiki settings page. Add description to all guild setting requirements work on optimizing conflict loading Add conflict purge commands for both deleted and user generated conflicts --- .../domains/subdomains/attack/v3/IAttack.java | 6 +- .../manager/v2/impl/SlashCommandManager.java | 1 - .../manager/v2/impl/pw/CommandManager2.java | 2 + .../v2/impl/pw/commands/ConflictCommands.java | 94 +++++++++++++++++++ .../v2/impl/pw/commands/StatCommands.java | 2 +- .../link/locutus/discord/db/NationDB.java | 30 +++++- .../discord/db/conflict/CoalitionSide.java | 3 +- .../locutus/discord/db/conflict/Conflict.java | 3 +- .../discord/db/conflict/ConflictManager.java | 38 ++++++-- .../locutus/discord/db/entities/DBWar.java | 32 +++---- .../locutus/discord/db/guild/GuildKey.java | 30 ++---- .../discord/db/guild/GuildSetting.java | 90 +++++++++++++----- .../util/update/WarUpdateProcessor.java | 23 ++--- .../locutus/discord/web/jooby/AwsManager.java | 2 +- .../link/locutus/wiki/CommandWikiPages.java | 24 +++++ .../link/locutus/wiki/WikiGenHandler.java | 1 + .../locutus/wiki/pages/WikiSettingsPage.java | 42 +++++++++ 17 files changed, 329 insertions(+), 94 deletions(-) create mode 100644 src/main/java/link/locutus/wiki/pages/WikiSettingsPage.java diff --git a/src/main/java/link/locutus/discord/apiv1/domains/subdomains/attack/v3/IAttack.java b/src/main/java/link/locutus/discord/apiv1/domains/subdomains/attack/v3/IAttack.java index 25b9355b..87c8441e 100644 --- a/src/main/java/link/locutus/discord/apiv1/domains/subdomains/attack/v3/IAttack.java +++ b/src/main/java/link/locutus/discord/apiv1/domains/subdomains/attack/v3/IAttack.java @@ -5,12 +5,14 @@ import link.locutus.discord.apiv1.enums.SuccessType; import link.locutus.discord.apiv3.enums.AttackTypeSubCategory; import link.locutus.discord.config.Settings; +import link.locutus.discord.db.entities.DBNation; import link.locutus.discord.db.entities.DBWar; import link.locutus.discord.util.PW; import link.locutus.discord.util.update.WarUpdateProcessor; import java.util.Collections; import java.util.List; +import java.util.function.BiFunction; public interface IAttack { /* @@ -171,7 +173,7 @@ default AbstractCursor getPriorAttack(boolean onlySameAttacker, boolean load) { DBWar getWar(); - default AttackTypeSubCategory getSubCategory(boolean checkActive) { - return WarUpdateProcessor.subCategorize(this, checkActive); + default AttackTypeSubCategory getSubCategory(BiFunction checkActiveM) { + return WarUpdateProcessor.subCategorize(this, checkActiveM); } } diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/SlashCommandManager.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/SlashCommandManager.java index 4bbab4bc..88477e55 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/SlashCommandManager.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/SlashCommandManager.java @@ -310,7 +310,6 @@ public static String getSlashCommand(String path, Map arguments, StringBuilder builder = new StringBuilder(); builder.append("/").append(path.toLowerCase(Locale.ROOT)); if (!arguments.isEmpty()) { - // join on " " for (Map.Entry entry : arguments.entrySet()) { if (entry.getValue() == null) continue; builder.append(" ").append(entry.getKey().toLowerCase(Locale.ROOT)).append(": ").append(entry.getValue()); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java index b45d962b..50661a27 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/CommandManager2.java @@ -203,6 +203,8 @@ public CommandManager2 registerDefaults() { getCommands().registerMethod(new VirtualConflictCommands(), List.of("conflict"), "createTemporary", "create_temp"); getCommands().registerMethod(new ConflictCommands(), List.of("conflict"), "deleteConflict", "delete"); getCommands().registerMethod(new ConflictCommands(), List.of("conflict"), "addConflict", "create"); + getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "purge"), "purgeFeatured", "featured"); + getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "purge"), "purgeTemporaryConflicts", "user_generated"); getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "edit"), "setConflictEnd", "end"); getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "edit"), "setConflictStart", "start"); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/ConflictCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/ConflictCommands.java index 70b59119..ea67030e 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/ConflictCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/ConflictCommands.java @@ -1,5 +1,6 @@ package link.locutus.discord.commands.manager.v2.impl.pw.commands; +import com.amazonaws.services.s3.model.S3ObjectSummary; import link.locutus.discord.commands.manager.v2.binding.annotation.*; import link.locutus.discord.commands.manager.v2.binding.annotation.Timestamp; import link.locutus.discord.commands.manager.v2.command.IMessageBuilder; @@ -781,6 +782,7 @@ public String removeFeature(ConflictManager manager, @Me GuildDB db, @Default Se @Command(desc = "List the ruleset for which conflicts are featured by this guild (if any are set)\n" + "This consists of a list of either guilds that created the conflict, or individual conflicts") + @CoalitionPermission(Coalition.MANAGE_CONFLICTS) public String listFeaturedRuleset(ConflictManager manager, @Me GuildDB db) { List config = manager.getSourceSets().get(db.getIdLong()); if (config == null || config.isEmpty()) { @@ -803,4 +805,96 @@ public String listFeaturedRuleset(ConflictManager manager, @Me GuildDB db) { } return "Featured conflicts:\n" + StringMan.join(items, "\n"); } + + @Command(desc = "Purge permenent conflicts that aren't in the database") + @RolePermission(Roles.MILCOM) + @CoalitionPermission(Coalition.MANAGE_CONFLICTS) + public String purgeFeatured(ConflictManager manager, @Me IMessageIO io, @Me JSONObject command, @Default @Timestamp Long olderThan, @Switch("f") boolean force) { + Set deleted = new LinkedHashSet<>(); + Set kept = new LinkedHashSet<>(); + for (S3ObjectSummary object : manager.getAws().getObjects()) { + Date date = object.getLastModified(); + if (olderThan != null && olderThan < date.getTime()) { + continue; + } + String key = object.getKey(); + boolean matches = key.matches("conflict/graphs/[0-9]+\\.gzip") || key.matches("conflict/[0-9]+\\.gzip"); + if (!matches) continue; + int id = Integer.parseInt(key.replaceAll("[^0-9]", "")); + Conflict conflict = manager.getConflictById(id); + if (conflict == null) { + deleted.add(id + ""); + if (force) { + manager.getAws().deleteObject(key); + } + } else { + kept.add(id + ""); + } + } + if (deleted.isEmpty()) { + if (kept.isEmpty()) { + return "No featured conflicts found on the website matching the database"; + } else { + return "No featured conflicts found on the website missing from the database\n" + + "Kept: " + StringMan.join(kept, ", "); + } + } + if (force) { + return "**Deleted:**\n- " + StringMan.join(deleted, "\n- ") + "\n" + + "**Kept:**\n- " + StringMan.join(kept, "\n- "); + } else { + StringBuilder body = new StringBuilder(); + body.append("Deleted " + deleted.size() + " conflicts\n"); + body.append("Kept: " + kept.size() + "/" + manager.getConflictMap().size() + "\n"); + io.create().confirmation("Deleted " + deleted.size() + " conflicts", body.toString(), command) + .file("deleted.txt", StringMan.join(deleted, "\n")) + .send(); + return null; + } + } + + @Command(desc = "Purge permenent conflicts that aren't in the database") + @RolePermission(Roles.MILCOM) + @CoalitionPermission(Coalition.MANAGE_CONFLICTS) + public String purgeTemporaryConflicts(ConflictManager manager, @Me IMessageIO io, @Me JSONObject command, @Timestamp long olderThan, @Switch("f") boolean force) { + List deleted = new ArrayList<>(); + List kept = new ArrayList<>(); + for (S3ObjectSummary object : manager.getAws().getObjects()) { + String key = object.getKey(); + boolean matches = key.matches("conflicts/n/[0-9]+/[a-z0-9]+\\.gzip") || key.matches("conflicts/graphs/n/[0-9]+/[a-z0-9]+\\.gzip"); + if (!matches) continue; + int nationId = Integer.parseInt(key.replaceAll("[^0-9]", "")); + String uuidStr = key.substring(key.lastIndexOf("/") + 1, key.lastIndexOf(".")); + String nameStr = PW.getMarkdownUrl(nationId, false) + "/" + uuidStr; + Date date = object.getLastModified(); + if (olderThan < date.getTime()) { + kept.add(nameStr); + continue; + } + deleted.add(nameStr); + if (force) { + manager.getAws().deleteObject(key); + } + } + if (deleted.isEmpty()) { + if (kept.isEmpty()) { + return "No featured conflicts found on the website matching the database"; + } else { + return "No featured conflicts found on the website missing from the database\n" + + "Kept: " + StringMan.join(kept, ", "); + } + } + if (force) { + return "**Deleted:**\n- " + StringMan.join(deleted, "\n- ") + "\n" + + "**Kept:**\n- " + StringMan.join(kept, "\n- "); + } else { + StringBuilder body = new StringBuilder(); + body.append("Deleted " + deleted.size() + " conflicts\n"); + body.append("Kept: " + kept.size() + "/" + manager.getConflictMap().size() + "\n"); + io.create().confirmation("Deleted " + deleted.size() + " conflicts", body.toString(), command) + .file("deleted.txt", StringMan.join(deleted, "\n")) + .send(); + return null; + } + } } diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/StatCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/StatCommands.java index 93c5e9df..143b3d48 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/StatCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/StatCommands.java @@ -2375,7 +2375,7 @@ public void attackBreakdownSheet(@Me IMessageIO io, @Me GuildDB db, Set new EnumMap<>(AttackTypeSubCategory.class)).merge(subType, 1, Integer::sum); diff --git a/src/main/java/link/locutus/discord/db/NationDB.java b/src/main/java/link/locutus/discord/db/NationDB.java index a175eaa2..23924bc4 100644 --- a/src/main/java/link/locutus/discord/db/NationDB.java +++ b/src/main/java/link/locutus/discord/db/NationDB.java @@ -6,11 +6,7 @@ import com.ptsmods.mysqlw.table.TablePreset; import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.ints.*; -import it.unimi.dsi.fastutil.longs.Long2DoubleLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArraySet; @@ -3690,6 +3686,30 @@ public Set getActivityByDay(int nationId, long minTurn) { return result; } + public Map> getActivityByDay(long minDate, long maxDate) { + // dates are inclusive + long minTurn = TimeUtil.getTurn(minDate); + long maxTurn = TimeUtil.getTurn(maxDate); + try (PreparedStatement stmt = prepareQuery("select nation, (`turn`/12) FROM ACTIVITY WHERE turn >= ? AND turn <= ?")) { + stmt.setLong(1, minTurn); + stmt.setLong(2, maxTurn); + + Map> result = new Int2ObjectOpenHashMap<>(); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + int id = rs.getInt(1); + long day = rs.getLong(2); + result.computeIfAbsent(id, f -> new LongOpenHashSet()).add(day); + } + } + return result; + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + public Map> getActivityByDay(long minDate, Predicate allowNation) { long minTurn = TimeUtil.getTurn(minDate); try (PreparedStatement stmt = prepareQuery("select nation, (`turn`/12) FROM ACTIVITY WHERE turn > ?")) { diff --git a/src/main/java/link/locutus/discord/db/conflict/CoalitionSide.java b/src/main/java/link/locutus/discord/db/conflict/CoalitionSide.java index 066803f4..6d77e9d3 100644 --- a/src/main/java/link/locutus/discord/db/conflict/CoalitionSide.java +++ b/src/main/java/link/locutus/discord/db/conflict/CoalitionSide.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -313,7 +314,7 @@ public void updateWar(DBWar previous, DBWar current, boolean isAttacker) { } } - public void updateAttack(DBWar war, AbstractCursor attack, boolean isAttacker, boolean checkActivity) { + public void updateAttack(DBWar war, AbstractCursor attack, boolean isAttacker, BiFunction checkActivity) { AttackTypeSubCategory subCategory = attack.getSubCategory(checkActivity); int attackerAA, attackerId, cities; long day = TimeUtil.getDay(attack.getDate()); diff --git a/src/main/java/link/locutus/discord/db/conflict/Conflict.java b/src/main/java/link/locutus/discord/db/conflict/Conflict.java index 9b4f32bd..567033be 100644 --- a/src/main/java/link/locutus/discord/db/conflict/Conflict.java +++ b/src/main/java/link/locutus/discord/db/conflict/Conflict.java @@ -41,6 +41,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -501,7 +502,7 @@ public boolean updateWar(DBWar previous, DBWar current, long turn) { return true; } - public void updateAttack(DBWar war, AbstractCursor attack, long turn, boolean checkActivity) { + public void updateAttack(DBWar war, AbstractCursor attack, long turn, BiFunction checkActivity) { int attackerAA, defenderAA; if (attack.getAttacker_id() == war.getAttacker_id()) { attackerAA = war.getAttacker_aa(); diff --git a/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java b/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java index 43877298..5d2deba3 100644 --- a/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java +++ b/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java @@ -16,6 +16,7 @@ import link.locutus.discord.config.Settings; import link.locutus.discord.db.WarDB; import link.locutus.discord.db.entities.DBAlliance; +import link.locutus.discord.db.entities.DBNation; import link.locutus.discord.db.entities.DBTopic; import link.locutus.discord.db.entities.DBWar; import link.locutus.discord.db.entities.conflict.ConflictCategory; @@ -51,6 +52,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -228,14 +230,14 @@ public void onAttack(AttackEvent event) { AbstractCursor attack = event.getAttack(); DBWar war = attack.getWar(); if (war != null) { - updateAttack(war, attack, f -> true); + updateAttack(war, attack, f -> true, DBNation::getActive_m); } } - public void updateAttack(DBWar war, AbstractCursor attack, Predicate allowed) { + public void updateAttack(DBWar war, AbstractCursor attack, Predicate allowed, BiFunction checkActivity) { long turn = TimeUtil.getTurn(war.getDate()); if (turn > lastTurn) initTurn(); - applyConflicts(allowed, turn, war.getAttacker_aa(), war.getDefender_aa(), f -> f.updateAttack(war, attack, turn, true)); + applyConflicts(allowed, turn, war.getAttacker_aa(), war.getDefender_aa(), f -> f.updateAttack(war, attack, turn, checkActivity)); } @Subscribe @@ -432,7 +434,7 @@ public void loadVirtualConflict(Conflict conflict, boolean clearBeforeUpdate) { db.iterateWarAttacks(wars, f -> true, f -> true, (war, attack) -> { long turn = TimeUtil.getTurn(attack.getDate()); if (TimeUtil.getTurn(war.getDate()) <= turn) { - conflict.updateAttack(war, attack, turn, false); + conflict.updateAttack(war, attack, turn, null); } }); } @@ -491,12 +493,28 @@ public void loadConflictWars(Collection conflicts, boolean clearBefore } System.out.println("Loaded wars in " + ((-start) + (start = System.currentTimeMillis()) + "ms")); - - db.iterateWarAttacks(wars, f -> true, f -> true, (war, attack) -> { - if (TimeUtil.getTurn(war.getDate()) <= TimeUtil.getTurn(attack.getDate())) { - updateAttack(war, attack, allowedConflicts); - } - }); + if (!wars.isEmpty()) { + Map> activity = Locutus.imp().getNationDB().getActivityByDay(startMs - TimeUnit.DAYS.toMillis(10), endMs); + System.out.println("Loaded activity in " + ((-start) + (start = System.currentTimeMillis()) + "ms")); + db.iterateWarAttacks(wars, f -> true, f -> true, (war, attack) -> { + if (TimeUtil.getTurn(war.getDate()) <= TimeUtil.getTurn(attack.getDate())) { + updateAttack(war, attack, allowedConflicts, new BiFunction() { + @Override + public Integer apply(DBNation nation, Long dateMs) { + Set natAct = activity.get(nation.getId()); + if (natAct == null) return Integer.MAX_VALUE; + long currDay = TimeUtil.getDay(dateMs); + for (long day = currDay; day >= currDay - 10; day--) { + if (natAct.contains(day)) { + return Math.toIntExact(TimeUnit.DAYS.toMinutes((int) (currDay - day))); + } + } + return Integer.MAX_VALUE; + } + }); + } + }); + } if (conflicts == null || conflicts.stream().anyMatch(f -> f.getId() != -1)) { db.query("SELECT * FROM conflict_graphs2", stmt -> { diff --git a/src/main/java/link/locutus/discord/db/entities/DBWar.java b/src/main/java/link/locutus/discord/db/entities/DBWar.java index b7904314..ef946479 100644 --- a/src/main/java/link/locutus/discord/db/entities/DBWar.java +++ b/src/main/java/link/locutus/discord/db/entities/DBWar.java @@ -33,7 +33,7 @@ public class DBWar { private byte warStatusType; private final long date; private char attDefCities; - private byte attDefActiveFlag; +// private byte attDefActiveFlag; public int getTurnsLeft() { return (int) (TimeUtil.getTurn() - TimeUtil.getTurn(getDate()) + 60); @@ -50,21 +50,21 @@ public DBWar(int warId, int attacker_id, int defender_id, int attacker_aa, int d this.attDefCities = (char) (attCities | (defCities << 8)); } - public boolean isAttActive() { - return (attDefActiveFlag & 0x01) == 1; - } - - public boolean isDefActive() { - return (attDefActiveFlag & 0x02) == 2; - } - - public void setAttActive(boolean value) { - attDefActiveFlag = (byte) (value ? attDefActiveFlag | 0x01 : attDefActiveFlag & 0xFE); - } - - public void setDefActive(boolean value) { - attDefActiveFlag = (byte) (value ? attDefActiveFlag | 0x02 : attDefActiveFlag & 0xFD); - } +// public boolean isAttActive() { +// return (attDefActiveFlag & 0x01) == 1; +// } +// +// public boolean isDefActive() { +// return (attDefActiveFlag & 0x02) == 2; +// } +// +// public void setAttActive(boolean value) { +// attDefActiveFlag = (byte) (value ? attDefActiveFlag | 0x01 : attDefActiveFlag & 0xFE); +// } +// +// public void setDefActive(boolean value) { +// attDefActiveFlag = (byte) (value ? attDefActiveFlag | 0x02 : attDefActiveFlag & 0xFD); +// } public int getAttCities() { return attDefCities & 0xFF; diff --git a/src/main/java/link/locutus/discord/db/guild/GuildKey.java b/src/main/java/link/locutus/discord/db/guild/GuildKey.java index f4fdd949..4a5c44ab 100644 --- a/src/main/java/link/locutus/discord/db/guild/GuildKey.java +++ b/src/main/java/link/locutus/discord/db/guild/GuildKey.java @@ -864,7 +864,7 @@ public void accept(GuildDB db) { } } - })); + }, "Requires having `" + GuildKey.ALLIANCE_ID.name() + "` set here, or `" + GuildKey.FA_SERVER.name() + "` set to this guild in another guild.")); public static GuildSetting>> ASSIGNABLE_ROLES = new GuildSetting>>(GuildSettingCategory.ROLE, Map.class, Role.class, TypeToken.getParameterized(Set.class, Role.class).getType()) { @NoFormat @Command(descMethod = "help") @@ -928,13 +928,7 @@ public String help() { "Members and `" + Roles.MILCOM.name() + "` are pinged for defensive wars\n" + "To set the `" + Roles.MILCOM.name() + "` role, see: " + CM.role.setAlias.cmd.create(Roles.MILCOM.name(), "", null, null); } - }.setupRequirements(f -> f.requiresAllies().requireActiveGuild().requireValidAlliance().requireFunction(new Consumer() { - @Override - public void accept(GuildDB guildDB) { - - // if ((!db.isValidAlliance() && !db.isWhitelisted() && !db.isOwnerActive()) || db.isDelegateServer()) continue; - } - })); + }.setupRequirements(f -> f.requiresAllies().requireActiveGuild().requireValidAlliance()); public static GuildSetting SHOW_ALLY_DEFENSIVE_WARS = new GuildBooleanSetting(GuildSettingCategory.WAR_ALERTS) { @NoFormat @Command(descMethod = "help") @@ -1420,12 +1414,7 @@ public String help() { return "If war rooms should be enabled (i.e. auto generate a channel for wars against active nations)\n" + "Note: Defensive war channels must be enabled to have auto war room creation"; } - }.setupRequirements(f -> f.requireFunction(new Consumer() { - @Override - public void accept(GuildDB db) { - db.getOrThrow(ALLIANCE_ID); - } - })); + }.setupRequirements(f -> f.requires(ALLIANCE_ID)); public static GuildSetting WAR_SERVER = new GuildSetting(GuildSettingCategory.WAR_ROOM, Guild.class) { @NoFormat @Command(descMethod = "help") @@ -1948,7 +1937,9 @@ public String help() { throw new IllegalArgumentException("Please use: " + CM.role.setAlias.cmd.toSlashMention() + " to set at least ONE of the following:\n" + StringMan.join(Arrays.asList(Roles.INTERVIEWER, Roles.MENTOR, Roles.INTERNAL_AFFAIRS_STAFF, Roles.INTERNAL_AFFAIRS), ", ")); } - })); + // to name + }, "Please set one of the roles:" + Arrays.asList(Roles.INTERVIEWER, Roles.MENTOR, Roles.INTERNAL_AFFAIRS_STAFF, Roles.INTERNAL_AFFAIRS) + .stream().map(Enum::name).collect(Collectors.joining(", ")) + " via " + CM.role.setAlias.cmd.toSlashMention())); public static GuildSetting ARCHIVE_CATEGORY = new GuildCategorySetting(GuildSettingCategory.INTERVIEW) { @NoFormat @Command(descMethod = "help") @@ -2304,7 +2295,7 @@ public String help() { if (offshoreDb == null || offshoreDb.getKey().getIdLong() != db.getIdLong()) { throw new IllegalArgumentException("This guild is not an offshore. See: " + CM.offshore.add.cmd.toSlashMention()); } - })); + }, "The guild must be a valid offshore")); public static GuildSetting> GRANT_TEMPLATE_BLACKLIST = new GuildSetting>(GuildSettingCategory.BANK_ACCESS, Set.class, Integer.class) { @Command(descMethod = "help") @@ -2721,15 +2712,12 @@ public String toReadableString(GuildDB db, List value) { return toJson(value).toString(4); } - }.setupRequirements(f -> f.requires(API_KEY).requires(ALLIANCE_ID).requireValidAlliance().requires(RECRUIT_MESSAGE_OUTPUT) + }.setupRequirements(f -> f.requires(API_KEY).requires(ALLIANCE_ID).requireValidAlliance().requiresCoalitionRoot("recruit").requires(RECRUIT_MESSAGE_OUTPUT) .requireFunction((db) -> { if (!Settings.INSTANCE.TASKS.CUSTOM_MESSAGE_HANDLER) { throw new IllegalArgumentException("This setting is disabled. Enable it in the settings file"); } - if (!db.hasCoalitionPermsOnRoot("recruit")) { - throw new IllegalArgumentException("You must have `recruit` permissions on the root channel to use this setting"); - } - })); + }, "")); public static GuildSetting ALLOW_UNVERIFIED_BANKING = new GuildBooleanSetting(GuildSettingCategory.BANK_ACCESS) { @NoFormat diff --git a/src/main/java/link/locutus/discord/db/guild/GuildSetting.java b/src/main/java/link/locutus/discord/db/guild/GuildSetting.java index adf2a668..27fe2882 100644 --- a/src/main/java/link/locutus/discord/db/guild/GuildSetting.java +++ b/src/main/java/link/locutus/discord/db/guild/GuildSetting.java @@ -47,8 +47,9 @@ public abstract class GuildSetting { private final Set requires = new LinkedHashSet<>(); private final Set requiresCoalitionStr = new LinkedHashSet<>(); + private final Set requiresCoalitionRootStr = new LinkedHashSet<>(); - private final Set> requiresFunction = new LinkedHashSet<>(); + private final Map, String> requiresFunction = new LinkedHashMap<>(); private final Map requiresRole = new LinkedHashMap<>(); private final Key type; @@ -57,6 +58,27 @@ public abstract class GuildSetting { private Queue>> setupRequirements = new ConcurrentLinkedQueue<>(); + public List getRequirementDesc() { + List reqListStr = new ArrayList<>(); + for (GuildSetting key : requires) { + reqListStr.add("setting:" + key.name()); + } + for (String coalition : requiresCoalitionStr) { + reqListStr.add("coalition:" + coalition); + } + for (String coalition : requiresCoalitionRootStr) { + reqListStr.add("coalition:" + coalition + "(root)"); + } + for (Roles role : requiresRole.keySet()) { + reqListStr.add("role:" + role.name()); + } + for (String value : requiresFunction.values()) { + if (value.isEmpty()) continue; + reqListStr.add("function:" + value); + } + return reqListStr; + } + public GuildSetting(GuildSettingCategory category, Type a, Type... subArgs) { this(category, TypeToken.getParameterized(a, subArgs).getType()); } @@ -114,6 +136,11 @@ public GuildSetting requiresCoalition(String coalition) { return this; } + public GuildSetting requiresCoalitionRoot(String coalition) { + requiresCoalitionRootStr.add(coalition.toLowerCase()); + return this; + } + public T validate(GuildDB db, T value) { return value; } @@ -126,7 +153,7 @@ public String getCommandFromArg(GuildDB db, Object value) { return getCommandObjRaw(db, value); } - private Set getCallables() { + public Set getCallables() { Set callables = Locutus.imp().getCommandManager().getV2().getCommands().getParametricCallables(f -> f.getObject() == this); callables.removeIf(f -> { String id = f.getPrimaryCommandId().toLowerCase(); @@ -267,6 +294,15 @@ public boolean allowed(GuildDB db, boolean throwException) { } } } + for (String coalition : requiresCoalitionRootStr) { + if (!db.hasCoalitionPermsOnRoot(coalition)) { + if (throwException) { + errors.add("You must first set the coalition `" + coalition + "` on the root server"); + } else { + return false; + } + } + } for (Map.Entry entry : requiresRole.entrySet()) { Roles role = entry.getKey(); @@ -281,7 +317,7 @@ public boolean allowed(GuildDB db, boolean throwException) { } } - for (BiPredicate predicate : requiresFunction) { + for (BiPredicate predicate : requiresFunction.keySet()) { try { if (!predicate.test(db, throwException)) { return false; @@ -303,12 +339,13 @@ public boolean allowed(GuildDB db, boolean throwException) { } public GuildSetting requiresOffshore() { - this.requiresFunction.add((db, throwError) -> { + String msg = "No bank is setup (see: " + CM.offshore.add.cmd.toSlashCommand() + ")"; + this.requiresFunction.put((db, throwError) -> { if (db.getOffshoreDB() == null) { - throw new IllegalArgumentException("No bank is setup (see: " + CM.offshore.add.cmd.toSlashCommand() + ")"); + throw new IllegalArgumentException(msg); } return true; - }); + }, msg); return this; } @@ -324,27 +361,29 @@ public GuildSetting requiresRole(Roles role, boolean allowAllianceRole) { } public GuildSetting requiresWhitelisted() { - this.requiresFunction.add(new BiPredicate() { + String msg = "This guild is not whitelisted by the bot developer (this feature may not be ready for public use yet)"; + this.requiresFunction.put(new BiPredicate() { @Override public boolean test(GuildDB db, Boolean throwError) { if (!db.isWhitelisted()) { - throw new IllegalArgumentException("This guild is not whitelisted by the bot developer (this feature may not be ready for public use yet)"); + throw new IllegalArgumentException(msg); } return true; } - }); + }, msg); return this; } public GuildSetting nonPublic() { - this.requiresFunction.add(new BiPredicate() { + String msg = "Please use the public channels for this (this is to reduce unnecessary discord calls)"; + this.requiresFunction.put(new BiPredicate() { @Override public boolean test(GuildDB db, Boolean throwError) { if (db.getGuild().getIdLong() == Settings.INSTANCE.ROOT_COALITION_SERVER) return true; if (db.hasCoalitionPermsOnRoot(Coalition.WHITELISTED)) return true; - throw new IllegalArgumentException("Please use the public channels for this (this is to reduce unnecessary discord calls)"); + throw new IllegalArgumentException(msg); } - }); + }, msg); return this; } @@ -376,12 +415,13 @@ public static Category validateCategory(GuildDB db, Category category) { } public GuildSetting requireValidAlliance() { - requiresFunction.add((db, throwError) -> { + String msg = "No valid alliance is setup (see: " + GuildKey.ALLIANCE_ID.getCommandMention() + ")"; + requiresFunction.put((db, throwError) -> { if (!db.isValidAlliance()) { - throw new IllegalArgumentException("No valid alliance is setup (see: " + GuildKey.ALLIANCE_ID.getCommandMention() + ")"); + throw new IllegalArgumentException(msg); } return true; - }); + }, msg); return this; } @@ -390,17 +430,18 @@ public GuildSetting requiresNot(GuildSetting setting) { } public GuildSetting requiresNot(GuildSetting setting, boolean checkDelegate) { - requiresFunction.add((db, throwError) -> { + String msg = "Cannot be used with " + setting.name() + " set. Unset via " + CM.settings.info.cmd.toSlashMention(); + requiresFunction.put((db, throwError) -> { if (setting.getOrNull(db, checkDelegate) != null) { - throw new IllegalArgumentException("Cannot be used with " + setting.name() + " set. Unset via " + CM.settings.info.cmd.toSlashMention()); + throw new IllegalArgumentException(msg); } return true; - }); + }, msg); return this; } - public GuildSetting requireFunction(Consumer predicate) { - this.requiresFunction.add((guildDB, throwError) -> { + public GuildSetting requireFunction(Consumer predicate, String msg) { + this.requiresFunction.put((guildDB, throwError) -> { try { predicate.accept(guildDB); } catch (IllegalArgumentException | UnsupportedOperationException e) { @@ -408,7 +449,7 @@ public GuildSetting requireFunction(Consumer predicate) { return false; } return true; - }); + }, msg); return this; } @@ -463,7 +504,7 @@ private void checkRegisteredOwnerOrActiveAlliance(GuildDB db) { public GuildSetting requireActiveGuild() { requireFunction(db -> { checkRegisteredOwnerOrActiveAlliance(db); - }); + }, "Guild owner must be registered to an active nation, or registered to an alliance with an active nation in a leader/heir position"); return this; } @@ -504,6 +545,7 @@ public String setAndValidate(GuildDB db, User user, T value) { } public GuildSetting requiresAllies() { + String msg = "No valid alliance or `" + Coalition.ALLIES + "` coalition exists. See: " + GuildKey.ALLIANCE_ID.getCommandMention() + " or " + CM.coalition.create.cmd.toSlashMention(); this.requireFunction(db -> { if (!db.isValidAlliance()) { boolean hasValidAllies = false; @@ -515,11 +557,11 @@ public GuildSetting requiresAllies() { } if (!hasValidAllies) { if (db.getCoalition(Coalition.ALLIES).isEmpty()) { - throw new IllegalArgumentException("No valid alliance or `" + Coalition.ALLIES + "` coalition exists. See: " + GuildKey.ALLIANCE_ID.getCommandMention() + " or " + CM.coalition.create.cmd.toSlashMention()); + throw new IllegalArgumentException(msg); } } } - }); + }, msg); return this; } } diff --git a/src/main/java/link/locutus/discord/util/update/WarUpdateProcessor.java b/src/main/java/link/locutus/discord/util/update/WarUpdateProcessor.java index 505e0ed1..2e8ffa8b 100644 --- a/src/main/java/link/locutus/discord/util/update/WarUpdateProcessor.java +++ b/src/main/java/link/locutus/discord/util/update/WarUpdateProcessor.java @@ -52,6 +52,7 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -402,14 +403,14 @@ public static AttackTypeSubCategory incrementCategory(AbstractCursor root, Map checkActiveM) { DBNation attacker = DBNation.getOrCreate(root.getAttacker_id()); DBNation defender = DBNation.getOrCreate(root.getDefender_id()); int attTanks = -1; @@ -444,14 +445,14 @@ public static AttackTypeSubCategory subCategorize(IAttack root, boolean checkAct if (attTanks > 0) { if (root.getDefcas3() == 0) { if (attSoldiers * 1.7_5 > enemyGroundStrength) { - if (!checkActive || defender.getActive_m(root.getDate()) < 10000) { + if (checkActiveM == null || checkActiveM.apply(defender, root.getDate()) < 10000) { return AttackTypeSubCategory.GROUND_TANKS_MUNITIONS_USED_UNNECESSARY; } else { return AttackTypeSubCategory.GROUND_NO_TANKS_MUNITIONS_USED_UNNECESSARY_INACTIVE; } } if (root.getMoney_looted() == 0 && root.getDefcas2() == 0 && defender.getUnitsAt(MilitaryUnit.AIRCRAFT, root.getDate()) == 0 && attacker.getUnitsAt(MilitaryUnit.AIRCRAFT, root.getDate()) > 3) { - if (!checkActive || defender.getActive_m(root.getDate()) < 10000) { + if (checkActiveM == null || checkActiveM.apply(defender, root.getDate()) < 10000) { return AttackTypeSubCategory.GROUND_TANKS_NO_LOOT_NO_ENEMY_AIR; } else { return AttackTypeSubCategory.GROUND_TANKS_NO_LOOT_NO_ENEMY_AIR_INACTIVE; @@ -464,7 +465,7 @@ public static AttackTypeSubCategory subCategorize(IAttack root, boolean checkAct if (root.getAtt_mun_used() == 0) { return AttackTypeSubCategory.GROUND_NO_MUNITIONS_NO_TANKS; } - if (!checkActive || defender.getActive_m(root.getDate()) < 10000) { + if (checkActiveM == null || checkActiveM.apply(defender, root.getDate()) < 10000) { return AttackTypeSubCategory.GROUND_NO_TANKS_MUNITIONS_USED_UNNECESSARY; } else { return AttackTypeSubCategory.GROUND_NO_TANKS_MUNITIONS_USED_UNNECESSARY_INACTIVE; @@ -505,8 +506,8 @@ else if (root.getAttack_type() == AIRSTRIKE_SHIP) { return AttackTypeSubCategory.AIRSTRIKE_FAILED_NOT_DOGFIGHT; } int active_m = -1; - if (checkActive && root.getAttack_type() == NAVAL) { - active_m = defender.getActive_m(root.getDate()); + if (checkActiveM != null && root.getAttack_type() == NAVAL) { + active_m = checkActiveM.apply(defender, root.getDate()); if (active_m > 10000) { defSoldiers = defender.getUnitsAt(MilitaryUnit.SOLDIER, root.getDate()); defTanks = defender.getUnitsAt(MilitaryUnit.TANK, root.getDate()); @@ -538,8 +539,8 @@ else if (root.getAttack_type() == AIRSTRIKE_SHIP) { } } } - if (active_m == -1 && checkActive) { - active_m = defender.getActive_m(root.getDate()); + if (active_m == -1 && checkActiveM != null) { + active_m = checkActiveM.apply(defender, root.getDate()); } if (active_m > 10000 && defender.getUnitsAt(MilitaryUnit.SHIP, root.getDate()) == 0) { return AttackTypeSubCategory.AIRSTRIKE_INACTIVE_NO_SHIP; @@ -552,13 +553,13 @@ else if (root.getAttack_type() == AIRSTRIKE_SHIP) { return AttackTypeSubCategory.AIRSTRIKE_3_PLANE; } if (root.getDefcas1() == 0) { - if (!checkActive || defender.getActive_m(root.getDate()) < 10000) { + if (checkActiveM == null || checkActiveM.apply(defender, root.getDate()) < 10000) { return AttackTypeSubCategory.AIRSTRIKE_AIRCRAFT_NONE; } else { return AttackTypeSubCategory.AIRSTRIKE_AIRCRAFT_NONE_INACTIVE; } } - if (checkActive && defender.getActive_m(root.getDate()) > 10000) { + if (checkActiveM != null && checkActiveM.apply(defender, root.getDate()) > 10000) { defSoldiers = defender.getUnitsAt(MilitaryUnit.SOLDIER, root.getDate()); defTanks = defender.getUnitsAt(MilitaryUnit.TANK, root.getDate()); if (defSoldiers == 0 && defTanks == 0) { diff --git a/src/main/java/link/locutus/discord/web/jooby/AwsManager.java b/src/main/java/link/locutus/discord/web/jooby/AwsManager.java index 80b72b2a..34033f47 100644 --- a/src/main/java/link/locutus/discord/web/jooby/AwsManager.java +++ b/src/main/java/link/locutus/discord/web/jooby/AwsManager.java @@ -92,7 +92,7 @@ public static void main(String[] args) { // System.out.println("* " + os.getKey() + " - size: " + os.getSize() + " - last modified: " + os.getLastModified()); // } - // move conflicts/7.gzip to conflicts/n/189573/1-2.gzip + // move conflicts/7.gzip to conflicts/n/189573/.gzip } diff --git a/src/main/java/link/locutus/wiki/CommandWikiPages.java b/src/main/java/link/locutus/wiki/CommandWikiPages.java index d9840386..433def9c 100644 --- a/src/main/java/link/locutus/wiki/CommandWikiPages.java +++ b/src/main/java/link/locutus/wiki/CommandWikiPages.java @@ -9,8 +9,10 @@ import link.locutus.discord.commands.manager.v2.command.CommandGroup; import link.locutus.discord.commands.manager.v2.command.ParametricCallable; import link.locutus.discord.commands.manager.v2.impl.discord.permission.RolePermission; +import link.locutus.discord.commands.manager.v2.impl.pw.refs.CM; import link.locutus.discord.commands.manager.v2.perm.PermissionHandler; import link.locutus.discord.config.yaml.Config; +import link.locutus.discord.db.guild.GuildSetting; import link.locutus.discord.util.MarkupUtil; import link.locutus.discord.util.StringMan; import retrofit2.http.HEAD; @@ -139,6 +141,28 @@ public static String printCommands(CommandGroup group, ValueStore store, Permiss return result.toString(); } + public static String printSettings(List settings) { + StringBuilder result = new StringBuilder(); + for (GuildSetting setting : settings) { + Set callables = setting.getCallables(); + List commandRefs = callables.stream().map(c -> c.getSlashCommand(Collections.emptyMap())).toList(); + if (commandRefs.isEmpty()) commandRefs = List.of(CM.settings.info.cmd.create(setting.name(), "", null).toString()); + + List requirementsStr = setting.getRequirementDesc(); + + result.append("## `").append(setting.name()).append("`\n"); + result.append("type: `" + setting.getType().toSimpleString() + "`\n"); + result.append("category: `" + setting.getCategory() + "`\n"); + result.append("Desc:\n```" + setting.help() + "```\n"); + result.append("commands:\n- " + String.join("\n- ", commandRefs) + "\n"); + if (!requirementsStr.isEmpty()) { + result.append("requirements:\n- " + String.join("\n- ", requirementsStr) + "\n"); + } + result.append("\n"); + } + return result.toString(); + } + private static String printPerms(Parser parser) { Annotation permAnnotation = parser.getKey().getAnnotations()[0]; diff --git a/src/main/java/link/locutus/wiki/WikiGenHandler.java b/src/main/java/link/locutus/wiki/WikiGenHandler.java index b1812ee4..2dc65f7a 100644 --- a/src/main/java/link/locutus/wiki/WikiGenHandler.java +++ b/src/main/java/link/locutus/wiki/WikiGenHandler.java @@ -37,6 +37,7 @@ public List getIntroPages() { public List getCommandPages() { List pages = new ArrayList<>(); pages.add(new WikiCommandsPage(manager)); + pages.add(new WikiSettingsPage(manager)); pages.add(new WikiArgumentsPage(manager)); pages.add(new WikiPermsPage(manager)); return pages; diff --git a/src/main/java/link/locutus/wiki/pages/WikiSettingsPage.java b/src/main/java/link/locutus/wiki/pages/WikiSettingsPage.java new file mode 100644 index 00000000..17ade89f --- /dev/null +++ b/src/main/java/link/locutus/wiki/pages/WikiSettingsPage.java @@ -0,0 +1,42 @@ +package link.locutus.wiki.pages; + +import link.locutus.discord.commands.manager.v2.impl.pw.CommandManager2; +import link.locutus.discord.commands.manager.v2.impl.pw.refs.CM; +import link.locutus.discord.db.guild.GuildKey; +import link.locutus.discord.db.guild.GuildSetting; +import link.locutus.wiki.BotWikiGen; +import link.locutus.wiki.CommandWikiPages; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class WikiSettingsPage extends BotWikiGen { + public WikiSettingsPage(CommandManager2 manager) { + super(manager, "commands"); + } + + @Override + public String getDescription() { + return "List and description of all commands."; + } + + @Override + public String generateMarkdown() { + List settings = new ArrayList<>(Arrays.asList(GuildKey.values())); + // sort by category, then by name + settings.sort(Comparator.comparing((GuildSetting a) -> a.getCategory()).thenComparing(GuildSetting::name)); + return build( + """ + This page lists all the settings that can be set on a guild. + To view a setting: + """, + commandMarkdownSpoiler(CM.settings.info.cmd), + "To delete a setting", + commandMarkdownSpoiler(CM.settings.delete.cmd), + "## Settings" + ) + + CommandWikiPages.printSettings(settings); + } +}