diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java index 3c0b8064..154fcae0 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/binding/PWBindings.java @@ -683,8 +683,9 @@ public static NationOrAllianceOrGuildOrTaxid nationOrAllianceOrGuildOrTaxId(Stri return selfDb; } } + boolean allowDeleted = data != null && data.getAnnotation(AllowDeleted.class) != null; try { - return nationOrAlliance(input, data); + return nationOrAlliance(data, input, allowDeleted); } catch (IllegalArgumentException ignore) { if (includeTaxId && !input.startsWith("#") && input.contains("tax_id")) { int taxId = PW.parseTaxId(input); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java index c0c9484a..f002a0e4 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/BankCommands.java @@ -2655,7 +2655,7 @@ public void largest(Map total, Map l @Command(desc = "Get a sheet of in-game transfers for nations") @RolePermission(value = Roles.ECON) - public String getIngameNationTransfers(@Me IMessageIO channel, @Me GuildDB db, Set senders, Set receivers, @Arg("Only transfers after timeframe") @Default("%epoch%") @Timestamp long timeframe, @Switch("s") SpreadSheet sheet) throws IOException, GeneralSecurityException { + public String getIngameNationTransfers(@Me IMessageIO channel, @Me GuildDB db, @AllowDeleted Set senders, @AllowDeleted Set receivers, @Arg("Only transfers after timeframe") @Default("%epoch%") @Timestamp long timeframe, @Switch("s") SpreadSheet sheet) throws IOException, GeneralSecurityException { if (sheet == null) sheet = SpreadSheet.create(db, SheetKey.BANK_TRANSACTION_SHEET); Set senderIds = senders.stream().map(NationOrAllianceOrGuild::getIdLong).collect(Collectors.toSet()); Set receiverIds = receivers.stream().map(NationOrAllianceOrGuild::getIdLong).collect(Collectors.toSet()); @@ -2800,7 +2800,7 @@ public String convertNegativeDeposits(@Me IMessageIO channel, @Me GuildDB db, @M }) @RolePermission(value = Roles.ECON) public String getNationsInternalTransfers(@Me IMessageIO channel, @Me GuildDB db, - Set nations, + @AllowDeleted Set nations, @Arg(value = "Only list transfers after this time", group = 0) @Timestamp @Default Long startTime, @Arg(value = "Only list transfers before this time", group = 0) @@ -2826,7 +2826,11 @@ public String getNationsInternalTransfers(@Me IMessageIO channel, @Me GuildDB db @Command(desc = "Get a sheet of transfers") @RolePermission(value = Roles.ECON, root = true) - public String getIngameTransactions(@Me IMessageIO channel, @Me GuildDB db, @Default NationOrAlliance sender, @Default NationOrAlliance receiver, @Default NationOrAlliance banker, @Default("%epoch%") @Timestamp long timeframe, @Switch("s") SpreadSheet sheet) throws GeneralSecurityException, IOException { + public String getIngameTransactions(@Me IMessageIO channel, @Me GuildDB db, + @AllowDeleted @Default NationOrAlliance sender, + @AllowDeleted @Default NationOrAlliance receiver, + @AllowDeleted @Default NationOrAlliance banker, + @Default("%epoch%") @Timestamp long timeframe, @Switch("s") SpreadSheet sheet) throws GeneralSecurityException, IOException { if (sheet == null) sheet = SpreadSheet.create(db, SheetKey.BANK_TRANSACTION_SHEET); List transactions = Locutus.imp().getBankDB().getAllTransactions(sender, receiver, banker, timeframe, null); if (transactions.size() > 10000) return "Timeframe is too large, please use a shorter period"; @@ -2841,7 +2845,7 @@ public String getIngameTransactions(@Me IMessageIO channel, @Me GuildDB db, @Def }) @RolePermission(value = Roles.ECON) public String transactions(@Me IMessageIO channel, @Me GuildDB db, @Me User user, - NationOrAllianceOrGuildOrTaxid nationOrAllianceOrGuild, + @AllowDeleted NationOrAllianceOrGuildOrTaxid nationOrAllianceOrGuild, @Arg(value = "Only show transactions after this time", group = 0) @Default("%epoch%") @Timestamp long timeframe, @Arg(value = "Do NOT include the tax record resources below the internal tax rate\n" + diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/EmbedCommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/EmbedCommands.java index 16f75e65..ddb95161 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/EmbedCommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/EmbedCommands.java @@ -103,7 +103,8 @@ public String title(@Me User user, @Me IMessageIO io, @Me Guild guild, Message d return null; } - @Command(desc = "Set the description of an embed from this bot") + @Command(desc = "Set the description of an embed from this bot\n" + + "Use backslash n for newline and `{description}` to include the existing description") @RolePermission(Roles.INTERNAL_AFFAIRS) public String description(@Me User user, @Me IMessageIO io, @Me Guild guild, Message discMessage, String description) { checkMessagePerms(user, guild, discMessage); @@ -113,7 +114,12 @@ public String description(@Me User user, @Me IMessageIO io, @Me Guild guild, Mes MessageEmbed embed = embeds.get(0); EmbedBuilder builder = new EmbedBuilder(embed); - builder.setDescription(description.replace("\\n", "\n")); + description = description.replace("\\n", "\n"); + String existing = embed.getDescription(); + if (existing != null && description.contains("{description}")) { + description = description.replace("{description}", existing); + } + builder.setDescription(description); message.clearEmbeds(); message.embed(builder.build()); diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/IACommands.java b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/IACommands.java index 5ebde8e7..b18d328e 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/IACommands.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/impl/pw/commands/IACommands.java @@ -128,7 +128,8 @@ public String renameInterviewChannels(@Me GuildDB db, @Me Guild guild, @Me User } else if (!allow_vm && nation.getVm_turns() > 0) { warnings.put(channel, "Nation is a VM: " + nation.getMarkdownUrl() + " (ignore with `allow_vm: True`"); } - if (channel.getTopic().isEmpty()) { + String topic = channel.getTopic(); + if (topic == null || channel.getTopic().isEmpty()) { String newTopic = nation.getUrl(); RateLimitUtil.queue(channel.getManager().setTopic(newTopic)); } diff --git a/src/main/java/link/locutus/discord/db/NationDB.java b/src/main/java/link/locutus/discord/db/NationDB.java index 67b9144c..5fcf7a8b 100644 --- a/src/main/java/link/locutus/discord/db/NationDB.java +++ b/src/main/java/link/locutus/discord/db/NationDB.java @@ -13,6 +13,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import link.locutus.discord.Locutus; import link.locutus.discord.apiv1.domains.subdomains.SNationContainer; +import link.locutus.discord.apiv1.enums.city.building.MilitaryBuilding; import link.locutus.discord.apiv3.csv.DataDumpParser; import link.locutus.discord.apiv3.csv.file.CitiesFile; import link.locutus.discord.apiv3.csv.file.NationsFile; @@ -1840,8 +1841,23 @@ public void markDirtyIncorrectNations(boolean score, boolean cities) { for (DBNation nation : getNationsMatching(f -> f.getVm_turns() == 0)) { if (score && Math.round(100 * (nation.estimateScore() - nation.getScore())) != 0) { dirtyNations.add(nation.getNation_id()); - } else if (cities && nation.getCities() != getCitiesV3(nation.getNation_id()).size()) { - dirtyNations.add(nation.getNation_id()); + } else { + Map cityMap = getCitiesV3(nation.getNation_id()); + if (cities && nation.getCities() != cityMap.size()) { + dirtyNations.add(nation.getNation_id()); + } else { + for (MilitaryUnit unit : new MilitaryUnit[]{MilitaryUnit.SOLDIER, MilitaryUnit.TANK, MilitaryUnit.AIRCRAFT, MilitaryUnit.SHIP}) { + MilitaryBuilding building = unit.getBuilding(); + int unitCap = 0; + for (DBCity city : cityMap.values()) { + unitCap += city.getBuilding(building) * building.getUnitCap() ; + } + if (nation.getUnits(unit) > unitCap) { + dirtyNations.add(nation.getNation_id()); + break; + } + } + } } } int added = dirtyNations.size() - originalSize; diff --git a/src/main/java/link/locutus/discord/db/entities/grant/AGrantTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/AGrantTemplate.java index 75b56f0a..f5230adf 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/AGrantTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/AGrantTemplate.java @@ -149,7 +149,7 @@ public String getCommandString() { expiryOrZero == 0 ? null : TimeUtil.secToTime(TimeUnit.MILLISECONDS, expiryOrZero), decayOrZero == 0 ? null : TimeUtil.secToTime(TimeUnit.MILLISECONDS, decayOrZero), allowIgnore ? "true" : null, - repeatable_time < 0 ? null : repeatable_time == 0 ? "0" : TimeUtil.secToTime(TimeUnit.MILLISECONDS, repeatable_time)); + repeatable_time <= 0 ? null : TimeUtil.secToTime(TimeUnit.MILLISECONDS, repeatable_time)); } public abstract String getCommandString(String name, String allowedRecipients, String econRole, String selfRole, String bracket, String useReceiverBracket, String maxTotal, String maxDay, String maxGranterDay, String maxGranterTotal, String allowExpire, String allowDecay, String allowIgnore, String repeatable); @@ -407,17 +407,19 @@ public Boolean apply(DBNation nation) { } })); } - - if (template == null || template.repeatable_time <= 0) { - // check nation not received grant already - list.add(new Grant.Requirement("Nation must NOT receive a grant template twice (when `repeatable: 0`)", false, new Function() { - @Override - public Boolean apply(DBNation nation) { - if (template == null) return true; - return db.getGrantTemplateManager().getRecordsByReceiver(nation.getId(), template.getName()).isEmpty(); + list.add(new Grant.Requirement("Nation must NOT receive a grant template twice (when `repeatable: false`)", false, new Function() { + @Override + public Boolean apply(DBNation nation) { + if (template == null) return true; + List records = db.getGrantTemplateManager().getRecordsByReceiver(nation.getId(), template.getName()); + if (template.repeatable_time <= 0) { + return records.isEmpty(); } - })); - } + long cutoff = System.currentTimeMillis() - template.repeatable_time; + records.removeIf(f -> f.date <= cutoff); + return records.isEmpty(); + } + })); // errors.computeIfAbsent(nation, f -> new ArrayList<>()).add("Nation was not found in guild"); list.add(new Grant.Requirement("Nation must be verified: " + CM.register.cmd.toSlashMention(), false, new Function() { diff --git a/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java index cfa90e11..0bc7d0bb 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/BuildTemplate.java @@ -12,6 +12,7 @@ import link.locutus.discord.db.entities.DBCity; import link.locutus.discord.db.entities.DBNation; import link.locutus.discord.db.entities.MMRInt; +import link.locutus.discord.db.entities.Transaction2; import link.locutus.discord.pnw.json.CityBuild; import link.locutus.discord.util.TimeUtil; import link.locutus.discord.util.offshore.Grant; @@ -307,10 +308,29 @@ public Boolean apply(DBNation receiver) { list.add(new Grant.Requirement("Nation must NOT have received a new city build grant (when `repeatable: False`)", false, new Function() { @Override public Boolean apply(DBNation receiver) { - if (template == null || template.getRepeatable() >= 0) return true; - if (template.allow_switch_after_offensive || template.allow_switch_after_infra || template.allow_switch_after_land_or_project) return true; - + if (template == null || db == null) return true; +// if (template.allow_switch_after_offensive || template.allow_switch_after_infra || template.allow_switch_after_land_or_project) return true; List records = db.getGrantTemplateManager().getRecordsByReceiver(receiver.getId()); + long repeatable = template.getRepeatable(); + if (repeatable > 0) { + long cutoff = System.currentTimeMillis() - repeatable; + records.removeIf(f -> f.date < cutoff); + } + long lastLand = 0; + long lastProject = 0; + long lastInfra = 0; + // get transfers + List> transactions = receiver.getTransactions(db, null, false, false, false, -1, 0, true); + for (Map.Entry entry : transactions) { + Transaction2 tx = entry.getValue(); + if (tx.receiver_id != receiver.getId()) continue; + String note = tx.note; + if (note == null || note.isEmpty()) continue; + if (note.contains("#project")) lastProject = Math.max(lastProject, tx.tx_datetime); + if (note.contains("#land")) lastLand = Math.max(lastLand, tx.tx_datetime); + if (note.contains("#infra")) lastInfra = Math.max(lastInfra, tx.tx_datetime); + } + long lastLandOrProject = Math.max(lastLand, lastProject); for(GrantTemplateManager.GrantSendRecord record : records) { if (template.allow_switch_after_days > 0 && record.date < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(template.allow_switch_after_days)) { @@ -319,6 +339,12 @@ public Boolean apply(DBNation receiver) { if (receiver.getCitiesSince(record.date) == 0 && record.grant_type == TemplateTypes.BUILD) { return false; } + if (template.allow_switch_after_infra && record.grant_type == TemplateTypes.INFRA && record.date > lastInfra) { + return false; + } + if (template.allow_switch_after_land_or_project && (record.grant_type == TemplateTypes.LAND || record.grant_type == TemplateTypes.PROJECT) && record.date > lastLandOrProject) { + return false; + } } return true; diff --git a/src/main/java/link/locutus/discord/db/entities/grant/CityTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/CityTemplate.java index 356d33b7..c4ba2d81 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/CityTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/CityTemplate.java @@ -35,7 +35,7 @@ public CityTemplate(GuildDB db, boolean isEnabled, String name, NationFilter nat // create new constructor with typed parameters instead of resultset public CityTemplate(GuildDB db, boolean isEnabled, String name, NationFilter nationFilter, long econRole, long selfRole, int fromBracket, boolean useReceiverBracket, int maxTotal, int maxDay, int maxGranterDay, int maxGranterTotal, long dateCreated, int min_city, int max_city, long expiryOrZero, long decayOrZero, boolean allowIgnore) { - super(db, isEnabled, name, nationFilter, econRole, selfRole, fromBracket, useReceiverBracket, maxTotal, maxDay, maxGranterDay, maxGranterTotal, dateCreated, expiryOrZero, decayOrZero, allowIgnore, 0); + super(db, isEnabled, name, nationFilter, econRole, selfRole, fromBracket, useReceiverBracket, maxTotal, maxDay, maxGranterDay, maxGranterTotal, dateCreated, expiryOrZero, decayOrZero, allowIgnore, -1); this.min_city = min_city; this.max_city = max_city; } diff --git a/src/main/java/link/locutus/discord/db/entities/grant/ProjectTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/ProjectTemplate.java index f7ca08ae..6c5c208a 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/ProjectTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/ProjectTemplate.java @@ -32,7 +32,7 @@ public ProjectTemplate(GuildDB db, boolean isEnabled, String name, NationFilter // create new constructor with typed parameters instead of resultset public ProjectTemplate(GuildDB db, boolean isEnabled, String name, NationFilter nationFilter, long econRole, long selfRole, int fromBracket, boolean useReceiverBracket, int maxTotal, int maxDay, int maxGranterDay, int maxGranterTotal, long dateCreated, Project project, long expiryOrZero, long decayOrZero, boolean allowIgnore) { - super(db, isEnabled, name, nationFilter, econRole, selfRole, fromBracket, useReceiverBracket, maxTotal, maxDay, maxGranterDay, maxGranterTotal, dateCreated, expiryOrZero, decayOrZero, allowIgnore, 0); + super(db, isEnabled, name, nationFilter, econRole, selfRole, fromBracket, useReceiverBracket, maxTotal, maxDay, maxGranterDay, maxGranterTotal, dateCreated, expiryOrZero, decayOrZero, allowIgnore, -1); this.project = project; } diff --git a/src/main/java/link/locutus/discord/db/entities/grant/RawsTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/RawsTemplate.java index ee2e687a..22fda4ca 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/RawsTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/RawsTemplate.java @@ -58,7 +58,7 @@ public String getCommandString(String name, String allowedRecipients, String eco allowExpire).decayTime( allowDecay).allowIgnore( allowIgnore).nonRepeatable( - getRepeatable() < 0 ? null : getRepeatable() == 0 ? "0" : TimeUtil.secToTime(TimeUnit.MILLISECONDS, getRepeatable())).toString(); + getRepeatable() <= 0 ? null : TimeUtil.secToTime(TimeUnit.MILLISECONDS, getRepeatable())).toString(); } @Override diff --git a/src/main/java/link/locutus/discord/db/entities/grant/WarchestTemplate.java b/src/main/java/link/locutus/discord/db/entities/grant/WarchestTemplate.java index 0b746a44..a5c67bc0 100644 --- a/src/main/java/link/locutus/discord/db/entities/grant/WarchestTemplate.java +++ b/src/main/java/link/locutus/discord/db/entities/grant/WarchestTemplate.java @@ -67,7 +67,7 @@ public String getCommandString(String name, String allowedRecipients, String eco allowExpire).decayTime( allowDecay).allowIgnore( allowIgnore).nonRepeatable( - getRepeatable() < 0 ? null : getRepeatable() == 0 ? "0" : TimeUtil.secToTime(TimeUnit.MILLISECONDS, getRepeatable())).toString(); + getRepeatable() <= 0 ? null : TimeUtil.secToTime(TimeUnit.MILLISECONDS, getRepeatable())).toString(); } @Override