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 a386ba28..7ab2686f 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 @@ -241,7 +241,8 @@ public CommandManager2 registerDefaults() { getCommands().registerMethod(new AdminCommands(), List.of("admin", "unset"), "unsetNews", "settings"); getCommands().registerMethod(new AdminCommands(), List.of("admin", "unset"), "unsetKeys", "news"); getCommands().registerMethod(new WarCommands(), List.of("alerts", "beige"), "testBeigeAlertAuto", "test_auto"); - getCommands().registerMethod(new UtilityCommands(), List.of("nation"), "vmHistory", "vm_history"); + getCommands().registerMethod(new UtilityCommands(), List.of("nation", "history"), "vmHistory", "vm"); + getCommands().registerMethod(new UtilityCommands(), List.of("nation", "history"), "gray_streak", "gray_streak"); getCommands().registerMethod(new UtilityCommands(), List.of("tax"), "setBracketBulk", "set_from_sheet"); getCommands().registerMethod(new StatCommands(), List.of("stats_other", "global_metrics"), "orbisStatByDay", "by_time"); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java index ebb3812f..a14243a8 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/UtilityCommands.java @@ -2223,6 +2223,91 @@ public String unitCost(Map units, return response.toString(); } + // Helper function to check and update inactivity streaks + private void checkAndUpdateStreak(Map lastActive, Map lastNotGray, Map lastStreak, Map countMap, int nationId, long day, int daysInactive) { + long lastActiveDay = lastActive.getOrDefault(nationId, day); + long lastNotGrayDay = lastNotGray.getOrDefault(nationId, day); + long lastActiveAdded = lastStreak.getOrDefault(nationId, 0L); + if (lastActiveAdded == lastActiveDay) return; + + if (day - lastNotGrayDay >= daysInactive) { + countMap.put(nationId, countMap.getOrDefault(nationId, 0) + 1); + lastStreak.put(nationId, lastActiveAdded); + } + } + + @Command(desc = "Get the inactivity streak of a set of nations over a specified timeframe") + public String grayStreak(@Me GuildDB db, @Me IMessageIO io, + Set nations, + int daysInactive, + @Timestamp long timeframe, + @Switch("s") SpreadSheet sheet) throws IOException, ParseException, GeneralSecurityException { + long minNationAge = Long.MAX_VALUE; + for (DBNation nation : nations) { + minNationAge = Math.min(minNationAge, nation.getDate()); + } + minNationAge = Math.max(minNationAge, timeframe); + Set nationIds = new IntOpenHashSet(nations.stream().map(DBNation::getNation_id).collect(Collectors.toSet())); + + DataDumpParser parser = Locutus.imp().getDataDumper(true); + List validDays = parser.getDays(true, false); + long today = TimeUtil.getDay(); + long finalMinDay = TimeUtil.getDay(minNationAge); + + Map lastActive = new Int2LongOpenHashMap(); + Map lastNotGray = new Int2LongOpenHashMap(); + Map lastStreak = new Int2ObjectOpenHashMap<>(); + Map countMap = new Int2ObjectOpenHashMap<>(); + + parser.iterateAll(f -> f >= finalMinDay, (h, r) -> r.required(h.nation_id).optional(h.vm_turns).required(h.color), null, new BiConsumer() { + @Override + public void accept(Long day, NationHeader header) { + int nationId = header.nation_id.get(); + if (!nationIds.contains(nationId)) return; + + NationColor color = header.color.get(); + if (color != NationColor.GRAY) { + lastNotGray.put(nationId, day); + if (color != NationColor.BEIGE) { + lastActive.put(nationId, day); + } + checkAndUpdateStreak(lastActive, lastNotGray, lastStreak, countMap, nationId, day, daysInactive); + } + } + }, null, new Consumer() { + @Override + public void accept(Long day) { + // Called when all nations have been processed for a day + } + }); + for (int nationId : nationIds) { + checkAndUpdateStreak(lastActive, lastNotGray, lastStreak, countMap, nationId, today, daysInactive); + } + + if (sheet == null) { + sheet = SpreadSheet.create(db, SheetKey.INACTIVITY_STREAK); + } + + List header = new ArrayList<>(Arrays.asList("nation", "alliance", "streak")); + sheet.setHeader(header); + for (DBNation nation : nations) { + int nationId = nation.getNation_id(); + int streak = countMap.getOrDefault(nationId, 0); + if (streak > 0) { + ArrayList row = new ArrayList<>(); + row.add(MarkupUtil.sheetUrl(nation.getNation(), nation.getUrl())); + row.add(MarkupUtil.sheetUrl(nation.getAllianceName(), nation.getAllianceUrl())); + row.add(streak); + sheet.addRow(row); + } + } + sheet.updateClearCurrentTab(); + sheet.updateWrite(); + sheet.attach(io.create(), "inactivity").send(); + return null; + } + + @Command(desc = "Get the VM history of a set of nations") public static String vmHistory(@Me IMessageIO io, Set nations, @Switch("s") SpreadSheet sheet) throws IOException, ParseException { if (nations.size() > 1000) { 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 a8d68e6d..475e2969 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 @@ -6464,18 +6464,6 @@ public unitHistory page(String value) { return set("page", value); } - } - @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.UtilityCommands.class,method="vmHistory") - public static class vm_history extends CommandRef { - public static final vm_history cmd = new vm_history(); - public vm_history nations(String value) { - return set("nations", value); - } - - public vm_history sheet(String value) { - return set("sheet", value); - } - } @AutoRegister(clazz=link.locutus.discord.commands.manager.v2.impl.pw.commands.WarCommands.class,method="wars") public static class wars extends CommandRef { diff --git a/src/main/java/link/locutus/discord/db/entities/DBNation.java b/src/main/java/link/locutus/discord/db/entities/DBNation.java index 28655758..9f9d6e1a 100644 --- a/src/main/java/link/locutus/discord/db/entities/DBNation.java +++ b/src/main/java/link/locutus/discord/db/entities/DBNation.java @@ -1854,6 +1854,26 @@ public long daysSinceConsecutiveLogins(long checkPastXDays, int sequentialDays) return Long.MAX_VALUE; } + @Command(desc = "Number of times since this nation's creation that they have been inactive for a specified number of days") + public int inactivity_streak(int daysInactive, long checkPastXDays) { + long turns = checkPastXDays * 12 + 11; + List logins = new ArrayList<>(Locutus.imp().getNationDB().getActivityByDay(nation_id, TimeUtil.getTurn(getSnapshot()) - turns)); + Collections.reverse(logins); + + int inactivityCount = 0; + long last = 0; + + for (long day : logins) { + long diff = last - day; + if (diff > daysInactive) { + inactivityCount++; + } + last = day; + } + + return inactivityCount; + } + @Command(desc = "Days since last bank deposit") public double daysSinceLastBankDeposit() { return (System.currentTimeMillis() - lastBankDeposit()) / (double) TimeUnit.DAYS.toMillis(1); 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 305d1949..5e7b4e74 100644 --- a/src/main/java/link/locutus/discord/db/guild/GuildSetting.java +++ b/src/main/java/link/locutus/discord/db/guild/GuildSetting.java @@ -7,6 +7,7 @@ import link.locutus.discord.commands.manager.v2.binding.LocalValueStore; import link.locutus.discord.commands.manager.v2.binding.Parser; import link.locutus.discord.commands.manager.v2.binding.ValueStore; +import link.locutus.discord.commands.manager.v2.binding.annotation.Command; import link.locutus.discord.commands.manager.v2.binding.annotation.Me; import link.locutus.discord.commands.manager.v2.command.ParameterData; import link.locutus.discord.commands.manager.v2.command.ParametricCallable; @@ -23,6 +24,7 @@ import link.locutus.discord.util.discord.DiscordUtil; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.Channel; import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.concrete.Category; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; @@ -516,6 +518,7 @@ public String getRaw(GuildDB db, boolean allowDelegate) { return db.getInfoRaw(this, allowDelegate); } + @Command(desc = "The setting category") public GuildSettingCategory getCategory() { return category; } @@ -564,4 +567,32 @@ public GuildSetting requiresAllies() { }, msg); return this; } + + @Command(desc = "Is this a channel setting") + public boolean isChannelType() { + return Channel.class.isAssignableFrom((Class) getType().getType()); + } + + @Command(desc = "The simple name of the setting class type") + public String getTypeName() { + return getType().getType().getTypeName(); + } + + @Command(desc = "The name of the setting type key") + public String getKeyName() { + return getType().toSimpleString(); + } + + @Command(desc = "The name of the setting") + public String getName() { + return name; + } + + @Command + public String getValueString(@Me GuildDB db) { + T value = getOrNull(db); + if (value == null) return null; + return toReadableString(db, value); + } + } diff --git a/src/main/java/link/locutus/discord/db/guild/SheetKey.java b/src/main/java/link/locutus/discord/db/guild/SheetKey.java index 63a9eb5f..6dba325b 100644 --- a/src/main/java/link/locutus/discord/db/guild/SheetKey.java +++ b/src/main/java/link/locutus/discord/db/guild/SheetKey.java @@ -58,6 +58,7 @@ public enum SheetKey { MERGES_SHEET, MILITARY_RANKING, FORUM_PROFILES, + INACTIVITY_STREAK, }