diff --git a/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/PrimitiveBindings.java b/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/PrimitiveBindings.java index b795f414..ae031bb2 100644 --- a/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/PrimitiveBindings.java +++ b/src/main/java/link/locutus/discord/commands/manager/v2/binding/bindings/PrimitiveBindings.java @@ -242,7 +242,11 @@ public static Long timestamp(String argument) throws ParseException { Date parsed = format.parse(argument); return parsed.getTime(); } - return System.currentTimeMillis() - TimeUtil.timeToSec(argument) * 1000; + try { + return System.currentTimeMillis() - TimeUtil.timeToSec(argument) * 1000; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid timestamp: `" + argument + "`: " + e.getMessage()); + } } @Binding 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 d831db73..22555620 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 @@ -38,6 +38,7 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import retrofit2.http.HEAD; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; @@ -576,7 +577,7 @@ public String importWikiPage(@Me GuildDB db, ConflictManager manager, if (conflict.getStartTurn() < TimeUtil.getTurn(1577836800000L)) { response.append("Conflict `" + name + "` before the war cutoff date of " + DiscordUtil.timestamp(1577836800000L, null) + "\n"); } else { - Set conflicts = loadWikiConflicts(db, manager, List.of(conflict), true, true); + Set conflicts = loadWikiConflicts(db, manager, new ArrayList<>(List.of(conflict)), true, true); List conflictNamesAndIds = conflicts.stream().map(f -> f.getName() + " - #" + f.getId()).toList(); response.append("Loaded conflict `" + name + "` with url `https://politicsandwar.com/wiki/" + url + "`\n"); if (conflictNamesAndIds.isEmpty()) { @@ -628,7 +629,7 @@ private Set loadWikiConflicts(GuildDB db, ConflictManager manager, Lis public String importWikiAll(@Me GuildDB db, ConflictManager manager, @Default("true") boolean useCache) throws IOException, ParseException { Map errors = new LinkedHashMap<>(); List conflicts = PWWikiUtil.loadWikiConflicts(errors, useCache); - Set loaded = loadWikiConflicts(db, manager, conflicts, false, false); + Set loaded = loadWikiConflicts(db, manager, new ArrayList<>(conflicts), false, false); return "Loaded " + loaded.size() + " conflicts from the wiki. Use " + CM.conflict.sync.website.cmd.create("*", "true", "true", "true") + " recalculate stats and push to the website."; } 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 286938eb..b3890250 100644 --- a/src/main/java/link/locutus/discord/db/conflict/Conflict.java +++ b/src/main/java/link/locutus/discord/db/conflict/Conflict.java @@ -728,6 +728,7 @@ public void addAnnouncement(String desc, DBTopic topic, boolean saveToDB) { announcements.put(desc, topic); if (saveToDB) { getManager().addAnnouncement(id, topic.topic_id, desc); + dirtyJson = true; } } 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 71d0963d..d1ce05d6 100644 --- a/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java +++ b/src/main/java/link/locutus/discord/db/conflict/ConflictManager.java @@ -211,35 +211,49 @@ private synchronized void initTurn() { return (conflict == null || conflict.getEndTurn() <= currTurn); }); recreateConflictsByAlliance(); - for (Conflict conflict : conflictArr) { long startTurn = Math.max(lastTurn + 1, conflict.getStartTurn()); long endTurn = Math.min(currTurn + 1, conflict.getEndTurn()); - if (startTurn >= endTurn) continue; - Set aaIds = conflict.getAllianceIds(); - for (long turn = startTurn; turn < endTurn; turn++) { - Map conflictIdsByAA = mapTurnAllianceConflictOrd.computeIfAbsent(turn, k -> new Int2ObjectOpenHashMap<>()); - for (int aaId : aaIds) { - if (conflict.getStartTurn(aaId) > turn) continue; - if (conflict.getEndTurn(aaId) <= turn) continue; - int[] currIds = conflictIdsByAA.get(aaId); - if (currIds == null) { - currIds = new int[]{conflict.getOrdinal()}; - } else { - int[] newIds = new int[currIds.length + 1]; - System.arraycopy(currIds, 0, newIds, 0, currIds.length); - newIds[currIds.length] = conflict.getOrdinal(); - Arrays.sort(newIds); - currIds = newIds; - } - conflictIdsByAA.put(aaId, currIds); - } - } + addAllianceTurn(conflict, startTurn, endTurn); } lastTurn = currTurn; } } + private void addAllianceTurn(Conflict conflict, long startTurn, long endTurn) { + if (startTurn >= endTurn) return; + synchronized (mapTurnAllianceConflictOrd) { + Set aaIds = conflict.getAllianceIds(); + for (long turn = startTurn; turn < endTurn; turn++) { + Map conflictIdsByAA = mapTurnAllianceConflictOrd.computeIfAbsent(turn, k -> new Int2ObjectOpenHashMap<>()); + for (int aaId : aaIds) { + addAllianceTurn(conflict, aaId, turn, conflictIdsByAA); + } + } + } + } + + private void addAllianceTurn(Conflict conflict, int aaId, long turn, Map conflictIdsByAA) { + if (conflict.getStartTurn(aaId) > turn) return; + if (conflict.getEndTurn(aaId) <= turn) return; + int[] currIds = conflictIdsByAA.get(aaId); + if (currIds == null) { + currIds = new int[]{conflict.getOrdinal()}; + } else { + int[] newIds = new int[currIds.length + 1]; + System.arraycopy(currIds, 0, newIds, 0, currIds.length); + newIds[currIds.length] = conflict.getOrdinal(); + Arrays.sort(newIds); + currIds = newIds; + } + conflictIdsByAA.put(aaId, currIds); + } + + private void addAllianceTurn(Conflict conflict, int aaId, long turn) { + Map conflictIdsByAA = mapTurnAllianceConflictOrd.computeIfAbsent(turn, k -> new Int2ObjectOpenHashMap<>()); + addAllianceTurn(conflict, aaId, turn, conflictIdsByAA); + } + public void clearAllianceCache() { synchronized (mapTurnAllianceConflictOrd) { mapTurnAllianceConflictOrd.clear(); @@ -360,17 +374,55 @@ private void recreateConflictsByAlliance() { synchronized (activeConflictOrdByAllianceId) { activeConflictOrdByAllianceId.clear(); for (int ord : activeConflictsOrd) { - addConflictsByAlliance(conflictArr[ord]); + addConflictsByAlliance(conflictArr[ord], false); } } } - private void addConflictsByAlliance(Conflict conflict) { + private void addConflictsByAlliance(Conflict conflict, boolean removeOld) { if (conflict == null) return; synchronized (activeConflictOrdByAllianceId) { + if (removeOld) { + Iterator>> iter = activeConflictOrdByAllianceId.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry> entry = iter.next(); + entry.getValue().remove(conflict.getOrdinal()); + if (entry.getValue().isEmpty()) { + iter.remove(); + } + } + synchronized (mapTurnAllianceConflictOrd) { + Iterator>> iter2 = mapTurnAllianceConflictOrd.entrySet().iterator(); + while (iter2.hasNext()) { + Map.Entry> entry = iter2.next(); + Iterator> iter3 = entry.getValue().entrySet().iterator(); + while (iter3.hasNext()) { + Map.Entry entry2 = iter3.next(); + int[] value = entry2.getValue(); + if (Arrays.binarySearch(value, conflict.getOrdinal()) >= 0) { + int[] newIds = new int[value.length - 1]; + if (newIds.length == 0) { + iter3.remove(); + } else { + for (int i = 0, j = 0; i < value.length; i++) { + if (value[i] != conflict.getOrdinal()) { + newIds[j++] = value[i]; + } + } + entry2.setValue(newIds); + } + } + } + if (entry.getValue().isEmpty()) { + iter2.remove(); + } + } + } + } for (int aaId : conflict.getAllianceIds()) { activeConflictOrdByAllianceId.computeIfAbsent(aaId, k -> new IntArraySet()).add(conflict.getOrdinal()); } + addAllianceTurn(conflict, conflict.getStartTurn(), Math.min(TimeUtil.getTurn(), conflict.getEndTurn())); } } @@ -1220,7 +1272,7 @@ public Conflict addConflict(String name, long creator, ConflictCategory category long turn = TimeUtil.getTurn(); if (turnEnd > turn) { activeConflictsOrd.add(conflict.getOrdinal()); - addConflictsByAlliance(conflict); + addConflictsByAlliance(conflict, false); } } @@ -1238,9 +1290,11 @@ protected void updateConflict(Conflict conflict, long start, long end) { if (activeConflictsOrd.contains(conflictOrd)) { if (end <= TimeUtil.getTurn()) { activeConflictsOrd.remove(conflictOrd); - recreateConflictsByAlliance(); } + } else if (!activeConflictsOrd.contains(conflictOrd) && end == Long.MAX_VALUE || end > TimeUtil.getTurn()) { + activeConflictsOrd.add(conflictOrd); } + addConflictsByAlliance(conflict, true); } db.update("UPDATE conflicts SET start = ?, end = ? WHERE id = ?", (ThrowingConsumer) stmt -> { stmt.setLong(1, start); @@ -1281,11 +1335,7 @@ protected void addParticipant(Conflict conflict, int allianceId, boolean side, l }); DBAlliance aa = DBAlliance.get(allianceId); if (aa != null) addLegacyName(allianceId, aa.getName(), System.currentTimeMillis()); - synchronized (activeConflictsOrd) { - if (activeConflictsOrd.contains(conflict.getOrdinal())) { - addConflictsByAlliance(conflict); - } - } + addConflictsByAlliance(conflict, true); } protected void removeParticipant(Conflict conflict, int allianceId) { @@ -1293,9 +1343,7 @@ protected void removeParticipant(Conflict conflict, int allianceId) { stmt.setInt(1, allianceId); stmt.setInt(2, conflict.getId()); }); - if (activeConflictsOrd.contains(conflict.getOrdinal())) { - recreateConflictsByAlliance(); - } + addConflictsByAlliance(conflict, true); } public Map getConflictMap() { diff --git a/src/main/java/link/locutus/discord/db/entities/conflict/DamageStatGroup.java b/src/main/java/link/locutus/discord/db/entities/conflict/DamageStatGroup.java index 7e9c1d06..87e3b0f7 100644 --- a/src/main/java/link/locutus/discord/db/entities/conflict/DamageStatGroup.java +++ b/src/main/java/link/locutus/discord/db/entities/conflict/DamageStatGroup.java @@ -160,9 +160,9 @@ public static Map> createHeade public void apply(AbstractCursor attack, boolean isAttacker) { attack.getLosses(totalCost, isAttacker, true, true, true, true, true); attack.getLosses(consumption, isAttacker, false, false, true, false, false); - attack.getLosses(loot, isAttacker, false, false, false, true, false); attack.getUnitLosses(units, isAttacker); if (!isAttacker) { + attack.getLosses(loot, false, false, false, false, true, false); infraCents += Math.round(attack.getInfra_destroyed_value() * 100); attack.addBuildingsDestroyed(buildings); } diff --git a/src/main/java/link/locutus/discord/web/commands/options/WebOptionBindings.java b/src/main/java/link/locutus/discord/web/commands/options/WebOptionBindings.java index cd1241fe..299b4075 100644 --- a/src/main/java/link/locutus/discord/web/commands/options/WebOptionBindings.java +++ b/src/main/java/link/locutus/discord/web/commands/options/WebOptionBindings.java @@ -2,6 +2,8 @@ import com.google.gson.JsonArray; import link.locutus.discord.Locutus; +import link.locutus.discord.apiv1.enums.city.project.Project; +import link.locutus.discord.apiv1.enums.city.project.Projects; import link.locutus.discord.commands.manager.v2.binding.BindingHelper; import link.locutus.discord.commands.manager.v2.binding.Parser; import link.locutus.discord.commands.manager.v2.binding.ValueStore; @@ -27,6 +29,7 @@ import java.awt.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -187,6 +190,11 @@ public WebOption getTaxBracket() { }); } //Project - return list Projects.values -> name() + @Binding(types = Project.class) + public WebOption getProject() { + List options = Arrays.stream(Projects.values).map(Project::name).toList(); + return new WebOption(Project.class).setOptions(options); + } //Building - return Buildings.values -> name() //DBLoan - locutus -> loan manager -> getloans diff --git a/src/main/java/link/locutus/wiki/game/PWWikiPage.java b/src/main/java/link/locutus/wiki/game/PWWikiPage.java index 5cdb9b31..039f45d2 100644 --- a/src/main/java/link/locutus/wiki/game/PWWikiPage.java +++ b/src/main/java/link/locutus/wiki/game/PWWikiPage.java @@ -153,8 +153,6 @@ public Map.Entry getDates() { return null; } - - public Map.Entry, Set> getCombatants(Set unknownCombatants, long dateStart) { List combatants = getTableData().get("combatants"); if (combatants == null || combatants.size() != 2) { diff --git a/src/main/java/link/locutus/wiki/game/PWWikiUtil.java b/src/main/java/link/locutus/wiki/game/PWWikiUtil.java index b7076cdd..18c65567 100644 --- a/src/main/java/link/locutus/wiki/game/PWWikiUtil.java +++ b/src/main/java/link/locutus/wiki/game/PWWikiUtil.java @@ -585,7 +585,7 @@ public static Conflict loadWikiConflict(String name, String urlStub, Map unknownAlliances = new LinkedHashSet<>(); Map.Entry, Set> combatants = page.getCombatants(unknownAlliances, date.getKey()); if (combatants == null) { - errorsByPage.put(name, "No combatants found"); + errorsByPage.put(name, "No combatants found (unknown: " + unknownAlliances + ")"); return null; } // if (!unknownAlliances.isEmpty()) {