From f18aea82c813005d394e9aa715b373fac2daadc4 Mon Sep 17 00:00:00 2001 From: test Date: Sat, 11 May 2024 18:08:11 +1000 Subject: [PATCH] Conflict guild rulesets Per guild featured rulesets for conflict visibility + associated commands. --- .../java/link/locutus/discord/Locutus.java | 12 +- .../manager/v2/impl/pw/CommandManager2.java | 4 + .../v2/impl/pw/commands/ConflictCommands.java | 118 +++++++++++++++--- .../pw/commands/VirtualConflictCommands.java | 5 +- .../commands/manager/v2/impl/pw/refs/CM.java | 27 +++- .../v2/impl/pw/refs/NationCommands.java | 7 ++ .../java/link/locutus/discord/db/GuildDB.java | 2 +- .../locutus/discord/db/conflict/Conflict.java | 77 +++++++++++- .../discord/db/conflict/ConflictManager.java | 65 ++++++++-- .../discord/db/conflict/CtownedFetcher.java | 9 +- .../locutus/discord/db/guild/GuildKey.java | 2 +- .../link/locutus/wiki/game/PWWikiUtil.java | 2 +- 12 files changed, 288 insertions(+), 42 deletions(-) diff --git a/src/main/java/link/locutus/discord/Locutus.java b/src/main/java/link/locutus/discord/Locutus.java index d1f5ad79..db630097 100644 --- a/src/main/java/link/locutus/discord/Locutus.java +++ b/src/main/java/link/locutus/discord/Locutus.java @@ -231,12 +231,14 @@ private Locutus() throws SQLException, ClassNotFoundException, LoginException, I throw new IllegalStateException("Please set API_KEY_PRIMARY or USERNAME/PASSWORD in " + Settings.INSTANCE.getDefaultFile()); } - Settings.INSTANCE.NATION_ID = 0; - Integer nationIdFromKey = Locutus.imp().getDiscordDB().getNationFromApiKey(Settings.INSTANCE.API_KEY_PRIMARY); - if (nationIdFromKey == null) { - throw new IllegalStateException("Could not get NATION_ID from key. Please ensure a valid API_KEY is set in " + Settings.INSTANCE.getDefaultFile()); + if (Settings.INSTANCE.NATION_ID <= 0) { + Settings.INSTANCE.NATION_ID = 0; + Integer nationIdFromKey = Locutus.imp().getDiscordDB().getNationFromApiKey(Settings.INSTANCE.API_KEY_PRIMARY); + if (nationIdFromKey == null) { + throw new IllegalStateException("Could not get NATION_ID from key. Please ensure a valid API_KEY is set in " + Settings.INSTANCE.getDefaultFile()); + } + Settings.INSTANCE.NATION_ID = nationIdFromKey; } - Settings.INSTANCE.NATION_ID = nationIdFromKey; System.out.println("remove:|| nationid " + (((-start)) + (start = System.currentTimeMillis()))); 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 2e1f6113..a1f5b935 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 @@ -192,6 +192,10 @@ public CommandManager2 registerDefaults() { getCommands().registerMethod(new UtilityCommands(), List.of("land"), "landROI", "roi"); getCommands().registerMethod(new UtilityCommands(), List.of("infra"), "infraROI", "roi"); + getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "featured"), "featureConflicts", "add_rule"); + getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "featured"), "removeFeature", "remove_rule"); + getCommands().registerMethod(new ConflictCommands(), List.of("conflict", "featured"), "listFeaturedRuleset", "list_rules"); + getCommands().registerMethod(new ConflictCommands(), List.of("conflict"), "info", "info"); getCommands().registerMethod(new ConflictCommands(), List.of("conflict"), "listConflicts", "list"); getCommands().registerMethod(new VirtualConflictCommands(), List.of("conflict"), "createTemporary", "create_temp"); 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 4e3e2f55..5a6fd623 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 @@ -7,6 +7,7 @@ import link.locutus.discord.commands.manager.v2.impl.discord.permission.WhitelistPermission; import link.locutus.discord.commands.manager.v2.impl.pw.refs.CM; import link.locutus.discord.config.Settings; +import link.locutus.discord.db.GuildDB; import link.locutus.discord.db.conflict.CoalitionSide; import link.locutus.discord.db.conflict.CtownedFetcher; import link.locutus.discord.db.entities.*; @@ -27,6 +28,7 @@ import link.locutus.discord.util.math.ArrayUtil; import link.locutus.discord.web.jooby.AwsManager; import link.locutus.discord.web.jooby.WebRoot; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import org.json.JSONObject; import org.jsoup.Jsoup; @@ -107,14 +109,14 @@ public String info(ConflictManager manager, Conflict conflict, boolean showParti @Command(desc = "Sets the wiki page for a conflict") @RolePermission(value = Roles.MILCOM) @CoalitionPermission(Coalition.MANAGE_CONFLICTS) - public String setWiki(ConflictManager manager, Conflict conflict, String url) throws IOException { + public String setWiki(@Me GuildDB db, ConflictManager manager, Conflict conflict, String url) throws IOException { if (url.startsWith("http")) { if (url.endsWith("/")) url = url.substring(0, url.length() - 1); url = url.substring(url.lastIndexOf("/") + 1); } conflict.setWiki(url); return "Set wiki to: `" + url + "`. Attempting to load additional wiki information...\n\n" + - importWikiPage(manager, conflict.getName(), url, false); + importWikiPage(db, manager, conflict.getName(), url, false); } @Command(desc = "Sets the wiki page for a conflict") @@ -328,7 +330,7 @@ private int getFighting(DBAlliance alliance, Set enemyCoalition) { @Command(desc = "Manually create an ongoing conflict between two coalitions") @RolePermission(Roles.MILCOM) @CoalitionPermission(Coalition.MANAGE_CONFLICTS) - public String addConflict(ConflictManager manager, @Me User user, ConflictCategory category, Set coalition1, Set coalition2, @Switch("n") String conflictName, @Switch("d") @Timestamp Long start) { + public String addConflict(@Me GuildDB db, ConflictManager manager, @Me User user, ConflictCategory category, Set coalition1, Set coalition2, @Switch("n") String conflictName, @Switch("d") @Timestamp Long start) { if (conflictName != null) { if (MathMan.isInteger(conflictName)) { throw new IllegalArgumentException("Conflict name cannot be a number (`" + conflictName + "`)"); @@ -379,7 +381,7 @@ public String addConflict(ConflictManager manager, @Me User user, ConflictCatego if (manager.getConflict(conflictName) != null) { throw new IllegalArgumentException("Conflict with name `" + conflictName + "` already exists"); } - Conflict conflict = manager.addConflict(conflictName, category, "Coalition 1", "Coalition 2", "", "", "", TimeUtil.getTurn(start), Long.MAX_VALUE); + Conflict conflict = manager.addConflict(conflictName, db.getIdLong(), category, "Coalition 1", "Coalition 2", "", "", "", TimeUtil.getTurn(start), Long.MAX_VALUE); StringBuilder response = new StringBuilder(); response.append("Created conflict `" + conflictName + "`\n"); // add coalitions @@ -508,12 +510,12 @@ public String addCoalition(@Me User user, Conflict conflict, Set all "This does not push the data to the site") @RolePermission(Roles.MILCOM) @CoalitionPermission(Coalition.MANAGE_CONFLICTS) - public String importCtowned(ConflictManager manager, + public String importCtowned(@Me GuildDB db, ConflictManager manager, @Default("true") @Arg("If the cached version of the site is used") boolean useCache) throws SQLException, IOException, ParseException, ClassNotFoundException { CtownedFetcher fetcher = new CtownedFetcher(manager); - fetcher.loadCtownedConflicts(useCache, ConflictCategory.NON_MICRO, "conflicts", "conflicts"); - fetcher.loadCtownedConflicts(useCache, ConflictCategory.MICRO, "conflicts/micros", "conflicts-micros"); + fetcher.loadCtownedConflicts(db, useCache, ConflictCategory.NON_MICRO, "conflicts", "conflicts"); + fetcher.loadCtownedConflicts(db, useCache, ConflictCategory.MICRO, "conflicts/micros", "conflicts-micros"); return "Done!\nNote: this does not push the data to the site"; } @@ -535,7 +537,10 @@ public String importAllianceNames(ConflictManager manager) throws IOException, P "This does not push the data to the site") @RolePermission(Roles.MILCOM) @CoalitionPermission(Coalition.MANAGE_CONFLICTS) - public String importWikiPage(ConflictManager manager, String name, @Default String url, @Default("true") boolean useCache) throws IOException { + public String importWikiPage(@Me GuildDB db, ConflictManager manager, + String name, + @Default String url, + @Default("true") boolean useCache) throws IOException { if (name.contains("http")) return "Please specify the name of the wiki page, not the URL for `name`"; if (url == null) { url = Normalizer.normalize(name.replace(" ", "_"), Normalizer.Form.NFKC); @@ -553,7 +558,7 @@ public String importWikiPage(ConflictManager manager, String name, @Default Stri if (conflict.getStartTurn() < TimeUtil.getTurn(1577836800000L)) { response.append("Conflict `" + name + "` before the war cutoff date of " + DiscordUtil.timestamp(1577836800000L, null) + "\n"); } else { - loadWikiConflicts(manager, List.of(conflict)); + loadWikiConflicts(db, manager, List.of(conflict)); response.append("Loaded conflict `" + name + "` with url `https://politicsandwar.com/wiki/" + url + "`\n"); response.append("See: " + CM.conflict.info.cmd.toSlashMention() + @@ -566,7 +571,7 @@ public String importWikiPage(ConflictManager manager, String name, @Default Stri return response.toString() + "\n\nNote: this does not push the data to the site"; } - private String loadWikiConflicts(ConflictManager manager, List conflicts) { + private String loadWikiConflicts(GuildDB db, ConflictManager manager, List conflicts) { Map> conflictsByWiki = manager.getConflictMap().values().stream().collect(Collectors.groupingBy(Conflict::getWiki, Collectors.toSet())); // Cutoff date conflicts.removeIf(f -> f.getStartTurn() < TimeUtil.getTurn(1577836800000L)); @@ -575,7 +580,7 @@ private String loadWikiConflicts(ConflictManager manager, List conflic Conflict original = conflict; Set existingSet = conflictsByWiki.get(conflict.getWiki()); if (existingSet == null) { - conflict = manager.addConflict(conflict.getName(), conflict.getCategory(), conflict.getSide(true).getName(), conflict.getSide(false).getName(), conflict.getWiki(), conflict.getCasusBelli(), conflict.getStatusDesc(), conflict.getStartTurn(), conflict.getEndTurn()); + conflict = manager.addConflict(conflict.getName(), db.getIdLong(), conflict.getCategory(), conflict.getSide(true).getName(), conflict.getSide(false).getName(), conflict.getWiki(), conflict.getCasusBelli(), conflict.getStatusDesc(), conflict.getStartTurn(), conflict.getEndTurn()); existingSet = Set.of(conflict); } for (Conflict existing : existingSet) { @@ -609,11 +614,11 @@ private String loadWikiConflicts(ConflictManager manager, List conflic "This does not push the data to the site") @RolePermission(Roles.MILCOM) @CoalitionPermission(Coalition.MANAGE_CONFLICTS) - public String importWikiAll(ConflictManager manager, @Default("true") boolean useCache) throws IOException { + public String importWikiAll(@Me GuildDB db, ConflictManager manager, @Default("true") boolean useCache) throws IOException { Map errors = new LinkedHashMap<>(); List conflicts = PWWikiUtil.loadWikiConflicts(errors, useCache); - return loadWikiConflicts(manager, conflicts); + return loadWikiConflicts(db, manager, conflicts); } @@ -642,7 +647,12 @@ public String recalculateGraphs(ConflictManager manager, Set conflicts "This does not push the data to the site") @RolePermission(Roles.MILCOM) @CoalitionPermission(Coalition.MANAGE_CONFLICTS) - public String importConflictData(ConflictManager manager, @Switch("c") boolean ctowned, @Switch("g") Set graphData, @Switch("a") boolean allianceNames, @Switch("w") boolean wiki, @Switch("s") boolean all) throws IOException, SQLException, ClassNotFoundException, ParseException { + public String importConflictData(@Me GuildDB db, ConflictManager manager, + @Switch("c") boolean ctowned, + @Switch("g") Set graphData, + @Switch("a") boolean allianceNames, + @Switch("w") boolean wiki, + @Switch("s") boolean all) throws IOException, SQLException, ClassNotFoundException, ParseException { boolean loadGraphData = false; if (all) { Locutus.imp().getWarDb().loadWarCityCountsLegacy(); @@ -655,10 +665,10 @@ public String importConflictData(ConflictManager manager, @Switch("c") boolean c importAllianceNames(manager); } if (ctowned) { - importCtowned(manager, true); + importCtowned(db, manager, true); } if (wiki) { - importWikiAll(manager, true); + importWikiAll(db, manager, true); } if (loadGraphData && graphData == null) { graphData = new LinkedHashSet<>(manager.getConflictMap().values()); @@ -669,4 +679,80 @@ public String importConflictData(ConflictManager manager, @Switch("c") boolean c } return "Done!\nNote: this does not push the data to the site"; } + + @Command(desc = "Configure the guilds and conflict ids that will be featured by this guild\n" + + "By default all conflicts are featured\n" + + "Specify either a set of conflicts, or a guild to feature all conflicts from that guild") + @RolePermission(Roles.MILCOM) + @CoalitionPermission(Coalition.MANAGE_CONFLICTS) + public String featureConflicts(ConflictManager manager, @Me GuildDB db, @Default Set conflicts, @Default Guild guild) { + if (conflicts == null && guild == null) { + throw new IllegalArgumentException("Please specify either `conflicts` or `guild`"); + } + List messages = new ArrayList<>(); + if (conflicts != null) { + for (Conflict conflict : conflicts) { + manager.addSource(db.getIdLong(), conflict.getId(), 0); + } + messages.add("Added " + conflicts.size() + " conflicts to the featured list"); + } + if (guild != null) { + manager.addSource(db.getIdLong(), guild.getIdLong(), 1); + messages.add("Added all conflicts from " + guild.toString() + " to the featured list"); + } + manager.pushIndex(); + messages.add("Pushed changed to site index"); + return StringMan.join(messages, "\n"); + } + + @Command(desc = "Configure the guilds and conflict ids that will be featured by this guild\n" + + "By default all conflicts are featured\n" + + "Specify either a set of conflicts, or a guild to feature all conflicts from that guild") + @RolePermission(Roles.MILCOM) + @CoalitionPermission(Coalition.MANAGE_CONFLICTS) + public String removeFeature(ConflictManager manager, @Me GuildDB db, @Default Set conflicts, @Default Guild guild) { + if (conflicts == null && guild == null) { + throw new IllegalArgumentException("Please specify either `conflicts` or `guild`"); + } + List messages = new ArrayList<>(); + if (conflicts != null) { + for (Conflict conflict : conflicts) { + manager.removeSource(db.getIdLong(), conflict.getId(), 0); + } + messages.add("Removed " + conflicts.size() + " conflicts from the featured list"); + } + if (guild != null) { + manager.removeSource(db.getIdLong(), guild.getIdLong(), 1); + messages.add("Removed all conflicts from " + guild.toString() + " from the featured list"); + } + manager.pushIndex(); + messages.add("Pushed changed to site index"); + messages.add("See: TODO CM REF to view the current featured conflict settings"); + return StringMan.join(messages, "\n"); + } + + @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") + public String listFeaturedRuleset(ConflictManager manager, @Me GuildDB db) { + List config = manager.getSourceSets().get(db.getIdLong()); + if (config == null || config.isEmpty()) { + return "This guild has no customized featured conflict ruleset. All conflicts are featured by default\n" + + "Use TODO CM REF to set what conflicts are featured"; + } + + List items = new ArrayList<>(); + + for (long id : config) { + String name; + if (id > Long.MAX_VALUE) { + GuildDB guild = Locutus.imp().getGuildDB(id); + name = guild == null ? "guild:" + id : guild.getGuild().toString(); + } else { + Conflict conflict = manager.getConflictById((int) id); + name = conflict == null ? "conflict:" + id : conflict.getName() + "/" + id; + } + items.add(name); + } + return "Featured conflicts:\n" + StringMan.join(items, "\n"); + } } diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/VirtualConflictCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/VirtualConflictCommands.java index b2eb6ac0..9d731bb9 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/VirtualConflictCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/VirtualConflictCommands.java @@ -10,6 +10,7 @@ import link.locutus.discord.db.entities.conflict.ConflictCategory; import link.locutus.discord.user.Roles; import link.locutus.discord.util.TimeUtil; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.User; import java.io.IOException; @@ -23,7 +24,7 @@ public class VirtualConflictCommands { @Command(desc = "Create a temporary conflict between two coalitions\n" + "Conflict is not auto updated") @RolePermission(Roles.ADMIN) - public String createTemporary(ConflictManager manager, @Me DBNation nation, @Me User author, Set col1, Set col2, @Timestamp long start, @Default @Timestamp Long end, @Switch("g") boolean includeGraphs) throws IOException, ParseException { + public String createTemporary(@Me Guild guild, ConflictManager manager, @Me DBNation nation, @Me User author, Set col1, Set col2, @Timestamp long start, @Default @Timestamp Long end, @Switch("g") boolean includeGraphs) throws IOException, ParseException { if (end == null) end = Long.MAX_VALUE; if (includeGraphs && (Math.min(System.currentTimeMillis(), end) - start > TimeUnit.DAYS.toMillis(90))) { throw new IllegalArgumentException("Graph generation is limited to 90 days of data. Please reduce the time range or set `includeGraphs: false`"); @@ -35,7 +36,7 @@ public String createTemporary(ConflictManager manager, @Me DBNation nation, @Me long turnStart = TimeUtil.getTurn(start); long turnEnd = end == Long.MAX_VALUE ? Long.MAX_VALUE : TimeUtil.getTurn(end); - Conflict conflict = new Conflict(-1, -1, ConflictCategory.GENERATED, + Conflict conflict = new Conflict(-1, -1, guild.getIdLong(), ConflictCategory.GENERATED, conflictName, col1Name, col2Name, diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/CM.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/CM.java index 000dee3b..7dd31d83 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/CM.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/CM.java @@ -1252,6 +1252,29 @@ public wiki create(String conflict, String url) { } } } + public static class featured{ + @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.ConflictCommands.class,method="featureConflicts") + public static class add_rule extends CommandRef { + public static final add_rule cmd = new add_rule(); + public add_rule create(String conflicts, String guild) { + return createArgs("conflicts", conflicts, "guild", guild); + } + } + @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.ConflictCommands.class,method="listFeaturedRuleset") + public static class list_rules extends CommandRef { + public static final list_rules cmd = new list_rules(); + public list_rules create() { + return createArgs(); + } + } + @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.ConflictCommands.class,method="removeFeature") + public static class remove_rule extends CommandRef { + public static final remove_rule cmd = new remove_rule(); + public remove_rule create(String conflicts, String guild) { + return createArgs("conflicts", conflicts, "guild", guild); + } + } + } @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.ConflictCommands.class,method="info") public static class info extends CommandRef { public static final info cmd = new info(); @@ -1305,8 +1328,8 @@ public recalculate_tables create(String conflicts) { @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.ConflictCommands.class,method="syncConflictData") public static class website extends CommandRef { public static final website cmd = new website(); - public website create(String conflicts, String upload_graphs, String reinitialize_wars, String reinitialize_graphs) { - return createArgs("conflicts", conflicts, "upload_graphs", upload_graphs, "reinitialize_wars", reinitialize_wars, "reinitialize_graphs", reinitialize_graphs); + public website create(String conflicts, String upload_graph, String reinitialize_wars, String reinitialize_graphs) { + return createArgs("conflicts", conflicts, "upload_graph", upload_graph, "reinitialize_wars", reinitialize_wars, "reinitialize_graphs", reinitialize_graphs); } } @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.ConflictCommands.class,method="importWikiAll") diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/NationCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/NationCommands.java index 2936b666..70802585 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/NationCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/refs/NationCommands.java @@ -1269,6 +1269,13 @@ public getUnits create(String unit) { return createArgs("unit", unit); } } + @AutoRegister(clazz=link.locutus.discord.db.entities.DBNation.class,method="getUnitsAt") + public static class getUnitsAt extends CommandRef { + public static final getUnitsAt cmd = new getUnitsAt(); + public getUnitsAt create(String unit, String date) { + return createArgs("unit", unit, "date", date); + } + } @AutoRegister(clazz=link.locutus.discord.db.entities.DBNation.class,method="getUpdateTZ") public static class getUpdateTZ extends CommandRef { public static final getUpdateTZ cmd = new getUpdateTZ(); diff --git a/src/main/java/link/locutus/discord/db/GuildDB.java b/src/main/java/link/locutus/discord/db/GuildDB.java index f6df81f0..63202b8f 100644 --- a/src/main/java/link/locutus/discord/db/GuildDB.java +++ b/src/main/java/link/locutus/discord/db/GuildDB.java @@ -1061,11 +1061,11 @@ public void createTables() { if (getTableColumns("INFO").stream().noneMatch(c -> c.equalsIgnoreCase("date_updated"))) { executeStmt("ALTER TABLE `INFO` ADD COLUMN date_updated BIGINT NOT NULL DEFAULT " + System.currentTimeMillis()); } - }; createDeletionsTables(); } + public void purgeOldInterviews(long cutoff) { update("DELETE FROM INTERVIEW_MESSAGES2 WHERE date_updated < ?", (ThrowingConsumer) stmt -> stmt.setLong(1, cutoff)); } 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 1e731a6b..10661fe2 100644 --- a/src/main/java/link/locutus/discord/db/conflict/Conflict.java +++ b/src/main/java/link/locutus/discord/db/conflict/Conflict.java @@ -14,7 +14,10 @@ import link.locutus.discord.apiv1.enums.MilitaryUnit; import link.locutus.discord.apiv1.enums.Rank; import link.locutus.discord.apiv3.csv.DataDumpParser; +import link.locutus.discord.commands.manager.v2.binding.annotation.Command; +import link.locutus.discord.config.Settings; import link.locutus.discord.db.DBNationSnapshot; +import link.locutus.discord.db.GuildDB; import link.locutus.discord.db.entities.DBAlliance; import link.locutus.discord.db.entities.DBCity; import link.locutus.discord.db.entities.DBNation; @@ -24,6 +27,7 @@ import link.locutus.discord.db.entities.conflict.ConflictColumn; import link.locutus.discord.db.entities.conflict.ConflictMetric; import link.locutus.discord.db.entities.conflict.DamageStatGroup; +import link.locutus.discord.pnw.AllianceList; import link.locutus.discord.util.TimeUtil; import link.locutus.discord.web.jooby.AwsManager; import link.locutus.discord.web.jooby.JteUtil; @@ -61,6 +65,7 @@ public class Conflict { private final Map announcements = new Object2ObjectLinkedOpenHashMap<>(); private String casusBelli = ""; private String statusDesc = ""; + private long createdByServer; public void clearWarData() { warsVsAlliance.clear();; @@ -70,9 +75,10 @@ public void clearWarData() { dirtyJson = true; } - public Conflict(int id, int ordinal, ConflictCategory category, String name, String col1, String col2, String wiki, String cb, String status, long turnStart, long turnEnd) { + public Conflict(int id, int ordinal, long createdByServer, ConflictCategory category, String name, String col1, String col2, String wiki, String cb, String status, long turnStart, long turnEnd) { this.id = id; this.ordinal = ordinal; + this.createdByServer = createdByServer; this.category = category; this.name = name; this.wiki = wiki == null ? "" : wiki; @@ -98,6 +104,7 @@ public void setStatus(String status) { getManager().setStatus(id, status); } + @Command(desc = "The conflict category") public ConflictCategory getCategory() { return category; } @@ -404,10 +411,12 @@ private Map warsVsAllianceJson() { return result; } + @Command(desc = "The status of the conflict") public String getStatusDesc() { return statusDesc; } + @Command(desc = "The casus belli of the conflict") public String getCasusBelli() { return casusBelli; } @@ -589,22 +598,35 @@ public Conflict removeParticipant(int allianceId) { return this; } + @Command(desc = "The id of the conflict in the database and website url") public int getId() { return id; } + @Command(desc = "The url of this conflict (if pushed live)") + public String getUrl() { + return Settings.INSTANCE.WEB.S3.SITE + "/conflict?id=" + id; + } + + @Command(desc = "The ordinal of the conflict (load order)") public int getOrdinal() { return ordinal; } + @Command(desc = "The name of the conflict") public String getName() { return name; } + @Command(desc = "The turn the conflict started\n" + + "Measured in 2h turns from unix epoch") public long getStartTurn() { return turnStart; } + @Command(desc = "The turn the conflict ends\n" + + "Measured in 2h turns from unix epoch\n" + + "If the conflict is ongoing, this will be Long.MAX_VALUE") public long getEndTurn() { return turnEnd; } @@ -617,6 +639,44 @@ public Set getCoalition2() { return coalition2.getAllianceIds(); } + @Command(desc = "If an alliance is a participant in the conflict") + public boolean isParticipant(DBAlliance alliance) { + return coalition1.hasAlliance(alliance.getId()) || coalition2.hasAlliance(alliance.getId()); + } + + @Command(desc = "A number representing the side an alliance is on in the conflict\n" + + "0 = No side, 1 = Primary/Coalition 1, 2 = Secondary/Coalition 2") + public int getSide(DBAlliance alliance) { + if (coalition1.hasAlliance(alliance.getId())) return 1; + if (coalition2.hasAlliance(alliance.getId())) return 2; + return 0; + } + + @Command(desc = "The start turn of the conflict in milliseconds since unix epoch") + public long getStartMS() { + return TimeUtil.getTimeFromTurn(turnStart); + } + + @Command(desc = "The end turn of the conflict in milliseconds since unix epoch") + public long getEndMS() { + return turnEnd == Long.MAX_VALUE ? Long.MAX_VALUE : TimeUtil.getTimeFromTurn(turnEnd); + } + + @Command(desc = "The alliance list of for the first coalition") + public AllianceList getCol1List() { + return new AllianceList(getCoalition1()); + } + + @Command(desc = "The alliance list of for the second coalition") + public AllianceList getCol2List() { + return new AllianceList(getCoalition2()); + } + + @Command(desc = "The alliance list for ALL participants") + public AllianceList getAllianceList() { + return new AllianceList(getAllianceIds()); + } + public Set getCoalition1Obj() { return coalition1.getAllianceIds().stream().map(DBAlliance::getOrCreate).collect(Collectors.toSet()); } @@ -642,14 +702,17 @@ public Set getAllianceIds() { return ids; } + @Command(desc = "The number of active wars in the conflict") public int getActiveWars() { return coalition1.getOffensiveStats(null, false).activeWars + coalition2.getOffensiveStats(null, false).activeWars; } + @Command(desc = "The number of total wars in the conflict") public int getTotalWars() { return coalition1.getOffensiveStats(null, false).totalWars + coalition2.getOffensiveStats(null, false).totalWars; } + @Command(desc = "The current damage dealt between the conflict's participants, using market prices") public double getDamageConverted(boolean isPrimary) { return (isPrimary ? coalition1 : coalition2).getInflicted().getTotalConverted(); } @@ -672,10 +735,21 @@ public Map getAnnouncementsList() { } } + @Command(desc = "The wiki link for this conflict (or null)") public String getWiki() { return wiki; } + @Command(desc = "The discord guild that created this conflict (or null)") + public GuildDB getGuild() { + return createdByServer <= 0 ? null : Locutus.imp().getGuildDB(createdByServer); + } + + @Command(desc = "The id of the discord guild that created this conflict") + public long getGuildId() { + return createdByServer; + } + public Map getAnnouncement() { synchronized (announcements) { return new Object2ObjectOpenHashMap<>(announcements); @@ -711,6 +785,7 @@ public List push(ConflictManager manager, String webIdOrNull, boolean in return urls; } + @Command(desc = "If the conflict has been updated since the last push") public boolean isDirty() { return this.dirtyJson; } 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 c6402acf..801d0e34 100644 --- a/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java +++ b/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -283,6 +284,7 @@ public void loadConflicts() { String name = rs.getString("name"); long startTurn = rs.getLong("start"); long endTurn = rs.getLong("end"); + long createdByGuild = rs.getLong("creator"); String wiki = rs.getString("wiki"); String col1 = rs.getString("col1"); String col2 = rs.getString("col2"); @@ -291,7 +293,7 @@ public void loadConflicts() { if (col2.isEmpty()) col2 = "Coalition 2"; String cb = rs.getString("cb"); String status = rs.getString("status"); - Conflict conflict = new Conflict(id, ordinal++, category, name, col1, col2, wiki, cb, status, startTurn, endTurn); + Conflict conflict = new Conflict(id, ordinal++, createdByGuild, category, name, col1, col2, wiki, cb, status, startTurn, endTurn); conflicts.add(conflict); conflictById.put(id, conflict); } @@ -873,7 +875,6 @@ public void setCb(int conflictId, String cb) { } public void createTables() { - System.out.println("Create tables"); // // drop table conflicts // db.executeStmt("DROP TABLE IF EXISTS conflict_participant"); // db.executeStmt("DROP TABLE IF EXISTS conflicts"); @@ -881,11 +882,13 @@ public void createTables() { // db.executeStmt("DROP TABLE IF EXISTS conflict_graphs2"); db.executeStmt("CREATE TABLE IF NOT EXISTS conflict_announcements2 (conflict_id INTEGER NOT NULL, topic_id INTEGER NOT NULL, description VARCHAR NOT NULL, PRIMARY KEY (conflict_id, topic_id), FOREIGN KEY(conflict_id) REFERENCES conflicts(id))"); - db.executeStmt("CREATE TABLE IF NOT EXISTS conflicts (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR NOT NULL, start BIGINT NOT NULL, end BIGINT NOT NULL, col1 VARCHAR NOT NULL, col2 VARCHAR NOT NULL, wiki VARCHAR NOT NULL, cb VARCHAR NOT NULL, status VARCHAR NOT NULL, category INTEGER NOT NULL)"); + db.executeStmt("CREATE TABLE IF NOT EXISTS conflicts (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR NOT NULL, start BIGINT NOT NULL, end BIGINT NOT NULL, col1 VARCHAR NOT NULL, col2 VARCHAR NOT NULL, wiki VARCHAR NOT NULL, cb VARCHAR NOT NULL, status VARCHAR NOT NULL, category INTEGER NOT NULL, creator BIGINT NOT NULL)"); + // add column `creator` // add col1 and col2 (string) to conflicts, default "" // db.executeStmt("ALTER TABLE conflicts ADD COLUMN col1 VARCHAR DEFAULT ''"); // db.executeStmt("ALTER TABLE conflicts ADD COLUMN col2 VARCHAR DEFAULT ''"); // add wiki column, default empty + db.executeStmt("ALTER TABLE conflicts ADD COLUMN creator BIGINT DEFAULT 0"); db.executeStmt("ALTER TABLE conflicts ADD COLUMN wiki VARCHAR DEFAULT ''"); db.executeStmt("ALTER TABLE conflicts ADD COLUMN cb VARCHAR DEFAULT ''"); db.executeStmt("ALTER TABLE conflicts ADD COLUMN status VARCHAR DEFAULT ''"); @@ -899,6 +902,50 @@ public void createTables() { db.executeStmt("CREATE TABLE IF NOT EXISTS conflict_graphs2 (conflict_id INTEGER NOT NULL, side BOOLEAN NOT NULL, alliance_id INT NOT NULL, metric INTEGER NOT NULL, turn BIGINT NOT NULL, city INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (conflict_id, alliance_id, metric, turn, city), FOREIGN KEY(conflict_id) REFERENCES conflicts(id))"); // drop conflict_graphs db.executeStmt("DROP TABLE conflict_graphs"); + + db.executeStmt("CREATE TABLE IF NOT EXISTS source_sets (guild BIGINT NOT NULL, source_id BIGINT NOT NULL, source_type INT NOT NULL, PRIMARY KEY (guild, source_id, source_type))"); + } + + public Map> getSourceSets() { + Map> sourceSets = new Long2ObjectOpenHashMap<>(); + db.query("SELECT * FROM source_sets", stmt -> { + }, (ThrowingConsumer) rs -> { + while (rs.next()) { + long guildId = rs.getLong("guild"); + long sourceId = rs.getLong("source_id"); + sourceSets.computeIfAbsent(guildId, f -> new LongArrayList()).add(sourceId); + } + }); + return sourceSets; + } + + /** + * type 0 = conflict + * type 1 = guild + * @param guild + * @param sourceId + * @param sourceType + */ + public void addSource(long guild, long sourceId, int sourceType) { + db.update("INSERT OR IGNORE INTO source_sets (guild, source_id, source_type) VALUES (?, ?, ?)", (ThrowingConsumer) stmt -> { + stmt.setLong(1, guild); + stmt.setLong(2, sourceId); + stmt.setInt(3, sourceType); + }); + } + + public void removeSource(long guild, long sourceId, int sourceType) { + db.update("DELETE FROM source_sets WHERE guild = ? AND source_id = ? AND source_type = ?", (ThrowingConsumer) stmt -> { + stmt.setLong(1, guild); + stmt.setLong(2, sourceId); + stmt.setInt(3, sourceType); + }); + } + + public void deleteAllSources(long guild) { + db.update("DELETE FROM source_sets WHERE guild = ?", (ThrowingConsumer) stmt -> { + stmt.setLong(1, guild); + }); } public void addAnnouncement(int conflictId, int topicId, String description) { @@ -975,8 +1022,8 @@ public void addLegacyName(int id, String name, long date) { } } - public Conflict addConflict(String name, ConflictCategory category, String col1, String col2, String wiki, String cb, String status, long turnStart, long turnEnd) { - String query = "INSERT INTO conflicts (name, col1, col2, wiki, start, end, category, cb, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + public Conflict addConflict(String name, long creator, ConflictCategory category, String col1, String col2, String wiki, String cb, String status, long turnStart, long turnEnd) { + String query = "INSERT INTO conflicts (name, col1, col2, wiki, start, end, category, cb, status, creator) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement stmt = db.getConnection().prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) { stmt.setString(1, name); stmt.setString(2, col1); @@ -987,11 +1034,12 @@ public Conflict addConflict(String name, ConflictCategory category, String col1, stmt.setInt(7, category.ordinal()); stmt.setString(8, cb); stmt.setString(9, status); + stmt.setLong(10, creator); stmt.executeUpdate(); ResultSet rs = stmt.getGeneratedKeys(); if (rs.next()) { int id = rs.getInt(1); - Conflict conflict = new Conflict(id, conflictArr.length, category, name, col1, col2, wiki, cb, status, turnStart, turnEnd); + Conflict conflict = new Conflict(id, conflictArr.length, creator, category, name, col1, col2, wiki, cb, status, turnStart, turnEnd); conflictById.put(id, conflict); conflictArr = Arrays.copyOf(conflictArr, conflictArr.length + 1); conflictArr[conflictArr.length - 1] = conflict; @@ -1188,7 +1236,6 @@ public Set getConflictNames() { public byte[] getPsonGzip() { Map root = new Object2ObjectLinkedOpenHashMap<>(); Map map = getConflictMap(); - System.out.println("Conflict map " + map.size()); Map aaNameById = new HashMap<>(); Map> headerFuncs = new LinkedHashMap<>(); @@ -1208,6 +1255,7 @@ public byte[] getPsonGzip() { headerFuncs.put("status", Conflict::getStatusDesc); headerFuncs.put("cb", Conflict::getCasusBelli); headerFuncs.put("posts", Conflict::getAnnouncementsList); + headerFuncs.put("source", Conflict::getGuildId); List headers = new ObjectArrayList<>(); List> funcs = new ObjectArrayList<>(); @@ -1215,8 +1263,6 @@ public byte[] getPsonGzip() { headers.add(entry.getKey()); funcs.add(entry.getValue()); } - root.put("headers", headers); - System.out.println("Headers " + root.get("headers")); List> rows = new ObjectArrayList<>(); JteUtil.writeArray(rows, funcs, map.values()); @@ -1235,6 +1281,7 @@ public byte[] getPsonGzip() { List aaNames = allianceIds.stream().map(aaNameById::get).toList(); root.put("alliance_ids", allianceIds); root.put("alliance_names", aaNames); + root.put("source_sets", getSourceSets()); return JteUtil.compress(JteUtil.toBinary(root)); } diff --git a/src/main/java/link/locutus/discord/db/conflict/CtownedFetcher.java b/src/main/java/link/locutus/discord/db/conflict/CtownedFetcher.java index a9b22b73..e504bc41 100644 --- a/src/main/java/link/locutus/discord/db/conflict/CtownedFetcher.java +++ b/src/main/java/link/locutus/discord/db/conflict/CtownedFetcher.java @@ -1,6 +1,7 @@ package link.locutus.discord.db.conflict; import link.locutus.discord.Locutus; +import link.locutus.discord.db.GuildDB; import link.locutus.discord.db.entities.conflict.ConflictCategory; import link.locutus.discord.util.StringMan; import link.locutus.discord.util.TimeUtil; @@ -73,7 +74,7 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) { } } - private void loadCtownedConflict(boolean useCache, String cellUrl, ConflictCategory category, String conflictName, Date startDate, Date endDate) throws IOException { + private void loadCtownedConflict(GuildDB db, boolean useCache, String cellUrl, ConflictCategory category, String conflictName, Date startDate, Date endDate) throws IOException { String conflictHtml = getCtoConflict(cellUrl, conflictName, useCache); long startMs = startDate.getTime(); @@ -148,7 +149,7 @@ private void loadCtownedConflict(boolean useCache, String cellUrl, ConflictCateg conflict = manager.getConflictMap().values().stream().filter(f -> finalWiki.equalsIgnoreCase(f.getWiki())).findFirst().orElse(null); } if (conflict == null) { - conflict = Locutus.imp().getWarDb().getConflicts().addConflict(conflictName, category, col1Name, col2Name, wiki, "", "", TimeUtil.getTurn(startMs), endMs == Long.MAX_VALUE ? Long.MAX_VALUE : TimeUtil.getTurn(endMs)); + conflict = Locutus.imp().getWarDb().getConflicts().addConflict(conflictName, db.getIdLong(), category, col1Name, col2Name, wiki, "", "", TimeUtil.getTurn(startMs), endMs == Long.MAX_VALUE ? Long.MAX_VALUE : TimeUtil.getTurn(endMs)); } if (conflict.getSide(true).getName().equalsIgnoreCase("coalition 1")) { conflict.setName(col1Name, true); @@ -165,7 +166,7 @@ private void loadCtownedConflict(boolean useCache, String cellUrl, ConflictCateg } } - public void loadCtownedConflicts(boolean useCache, ConflictCategory category, String urlStub, String fileName) throws IOException, SQLException, ClassNotFoundException, ParseException { + public void loadCtownedConflicts(GuildDB db, boolean useCache, ConflictCategory category, String urlStub, String fileName) throws IOException, SQLException, ClassNotFoundException, ParseException { Document document = Jsoup.parse(getCtoConflict(urlStub, fileName, useCache)); // get table id=conflicts-table Element table = document.getElementById("conflicts-table"); @@ -187,7 +188,7 @@ public void loadCtownedConflicts(boolean useCache, ConflictCategory category, St String endDateStr = row.select("td").get(7).text(); Date startDate = TimeUtil.YYYY_MM_DD_FORMAT.parse(startDateStr); Date endDate = endDateStr.contains("Ongoing") ? null : TimeUtil.YYYY_MM_DD_FORMAT.parse(endDateStr); - loadCtownedConflict(useCache, cellUrl, category, conflictName, startDate, endDate); + loadCtownedConflict(db, useCache, cellUrl, category, conflictName, startDate, endDate); } } } 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 98b8ea14..0b8bd713 100644 --- a/src/main/java/link/locutus/discord/db/guild/GuildKey.java +++ b/src/main/java/link/locutus/discord/db/guild/GuildKey.java @@ -2742,7 +2742,7 @@ public String help() { @NoFormat @Command(descMethod = "help") @RolePermission(Roles.ADMIN) - public String MEMBER_CAN_WITHDRAW(@Me GuildDB db, @Me User user, boolean enabled) { + public String MEMBER_CAN_ESCROW(@Me GuildDB db, @Me User user, boolean enabled) { return MEMBER_CAN_ESCROW.setAndValidate(db, user, enabled); } @Override diff --git a/src/main/java/link/locutus/wiki/game/PWWikiUtil.java b/src/main/java/link/locutus/wiki/game/PWWikiUtil.java index 03ef468a..6b48fd86 100644 --- a/src/main/java/link/locutus/wiki/game/PWWikiUtil.java +++ b/src/main/java/link/locutus/wiki/game/PWWikiUtil.java @@ -618,7 +618,7 @@ public static Conflict loadWikiConflict(String name, String urlStub, Map conflict.addParticipant(allianceId, true, false, null, null)); combatants.getValue().forEach(allianceId -> conflict.addParticipant(allianceId, false, false, null, null)); for (Map.Entry topicEntry : page.getForumLinks().entrySet()) {