Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ loader_version=0.19.3
fabric_version=0.141.4+1.21.11
loom_version=1.17.8

mod_version=1.6.3
mod_version=1.6.4-beta2
maven_group=tel.eden
archives_base_name=edenmod
modmenu_version=17.0.0
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/assets/edenmod/textures/gui/icons/nol.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/assets/edenmod/textures/gui/icons/notg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/assets/edenmod/textures/gui/icons/tcc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/assets/edenmod/textures/gui/icons/tna.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/assets/edenmod/textures/gui/icons/wtp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 111 additions & 4 deletions src/tel/eden/mod/EdenModClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import tel.eden.mod.gui.BridgeConfigScreen;
import tel.eden.mod.gui.CommandAliasScreen;
import tel.eden.mod.gui.CommandKeybindScreen;
import tel.eden.mod.gui.LongEventScreen;
import tel.eden.mod.gui.PartyCreateScreen;
import tel.eden.mod.gui.PartyListScreen;
import tel.eden.mod.gui.PartyManageScreen;
Expand All @@ -51,6 +52,7 @@
import tel.eden.mod.item.ItemStringDetector;
import tel.eden.mod.net.AllyActivityEntry;
import tel.eden.mod.net.BridgeWebSocketClient;
import tel.eden.mod.net.LongEventState;
import tel.eden.mod.net.PartyInfo;
import tel.eden.mod.net.GiveawayCandidate;
import tel.eden.mod.net.MailEntry;
Expand Down Expand Up @@ -168,6 +170,8 @@ public final class EdenModClient implements ClientModInitializer {
private final java.util.concurrent.atomic.AtomicInteger partyCommandGeneration = new java.util.concurrent.atomic.AtomicInteger();
private final java.util.concurrent.atomic.AtomicLong allyViewRequestId = new java.util.concurrent.atomic.AtomicLong();
private final java.util.concurrent.atomic.AtomicLong allyViewPendingId = new java.util.concurrent.atomic.AtomicLong();
private final java.util.concurrent.atomic.AtomicLong eventRewardsRequestId = new java.util.concurrent.atomic.AtomicLong();
private final java.util.concurrent.atomic.AtomicLong eventRewardsPendingId = new java.util.concurrent.atomic.AtomicLong();
// Deterministic per-occurrence index so rapid identical bank events stay distinct.
// Short window: only the current burst counts, so stale earlier deposits can't
// make different clients assign divergent seqs (which duplicated Discord posts).
Expand Down Expand Up @@ -270,6 +274,11 @@ public boolean onWynncraft() {
private volatile int giveawayStorageAspects;
private volatile String giveawayError;
private final java.util.concurrent.atomic.AtomicInteger giveawayGeneration = new java.util.concurrent.atomic.AtomicInteger();
private volatile LongEventState knownLongEventState;
// Last long-event error (a refused reroll, a failed request), shown briefly on the Event screen.
private volatile String longEventError = "";
private volatile long longEventErrorAtMs;
private final java.util.concurrent.atomic.AtomicInteger longEventGeneration = new java.util.concurrent.atomic.AtomicInteger();
// GitHub update check: run once per game session; the prompt offers a one-click
// download (applied on game close) and a link to the release page.
private final UpdateChecker updateChecker = new UpdateChecker();
Expand Down Expand Up @@ -506,6 +515,21 @@ public int giveawayGeneration() {
return giveawayGeneration.get();
}

/** Latest long-event mission state from the backend, or {@code null} before first reply. */
public LongEventState knownLongEventState() {
return knownLongEventState;
}

/** The last long-event error if it arrived in the past 5 seconds, else {@code ""}. */
public String recentLongEventError() {
return System.currentTimeMillis() - longEventErrorAtMs < 5_000L ? longEventError : "";
}

/** Bumped on every long-event state snapshot; 0 means none has arrived yet. */
public int longEventGeneration() {
return longEventGeneration.get();
}

/** The guild reward helper (rank lookups + gifting). */
public GuildRewards guildRewards() {
return guildRewards;
Expand Down Expand Up @@ -706,6 +730,7 @@ public void release() {
tel.eden.mod.war.radiance.RadianceTracker.onJoin();
tel.eden.mod.war.eco.TerritoryGuideController.reset();
guildAllyManager.requestCancel();
eventRewardsPendingId.set(0);
// Connect if applicable; the backend reports link/membership standing via
// authOk (handleAuthStatus), which drives any "please link" prompt.
evaluateGating(client);
Expand Down Expand Up @@ -742,7 +767,7 @@ public void release() {
}).then(ClientCommandManager.literal("download").executes(ctx -> {
updateDownload(ctx.getSource());
return 1;
}))).then(buildGiftCommand()).then(buildDeductCommand()).then(buildAllyCommand()).then(ClientCommandManager.literal("dump").then(ClientCommandManager.argument("member", StringArgumentType.word()).suggests(this::suggestMembers).executes(ctx -> dumpEmeralds(ctx.getSource(), StringArgumentType.getString(ctx, "member"))))).then(ClientCommandManager.literal("wars").executes(ctx -> {
}))).then(buildGiftCommand()).then(buildDeductCommand()).then(buildAllyCommand()).then(buildEventCommand()).then(ClientCommandManager.literal("dump").then(ClientCommandManager.argument("member", StringArgumentType.word()).suggests(this::suggestMembers).executes(ctx -> dumpEmeralds(ctx.getSource(), StringArgumentType.getString(ctx, "member"))))).then(ClientCommandManager.literal("wars").executes(ctx -> {
requestWarCounts(ctx.getSource(), 7);
return 1;
}).then(ClientCommandManager.argument("days", com.mojang.brigadier.arguments.IntegerArgumentType.integer(1, 365)).executes(ctx -> {
Expand Down Expand Up @@ -1228,7 +1253,7 @@ public void onConnectionRejected(String code) {
}

@Override
public void onPillMessage(String label, String content, String colorHex, boolean bold) {
public void onPillMessage(String label, String content, String colorHex, boolean bold, boolean own) {
if (GAMES_PILL_LABEL.equals(label) && config.gameDisplayMode != BridgeConfig.GameDisplayMode.ALL) {
// React Only still shows your own coinflip/diceroll result; every
// other "eden"-labelled message (streaks, gambit resets, reward
Expand All @@ -1250,6 +1275,15 @@ public void onPillMessage(String label, String content, String colorHex, boolean
}
}
Integer finalColorRgb = colorRgb;
if ("EVENT".equals(label)) {
// The bot flags your own completions (own), so this never depends on name matching.
if (!own && !config.eventAnnouncements) {
return;
}
String finalContent = own ? formatOwnEventAnnouncement(content) : content;
display(() -> DiscordChatFormatter.pill(label, finalContent, finalColorRgb, bold));
return;
}
display(() -> DiscordChatFormatter.pill(label, content, finalColorRgb, bold));
}

Expand Down Expand Up @@ -1298,6 +1332,31 @@ public void onAllyActivity(java.util.List<AllyActivityEntry> entries) {
display(() -> DiscordChatFormatter.allyActivity(entries, canManage));
}

@Override
public void onLongEventState(LongEventState state, boolean hasSnapshot) {
if (state.error() != null && !state.error().isEmpty()) {
longEventError = state.error();
longEventErrorAtMs = System.currentTimeMillis();
display(() -> DiscordChatFormatter.systemLine(state.error(), ChatFormatting.RED));
}
if (hasSnapshot || knownLongEventState == null) {
knownLongEventState = state;
longEventGeneration.incrementAndGet();
}
}

@Override
public void onLongEventRewards(int pendingLe, String error) {
if (eventRewardsPendingId.getAndSet(0) == 0) {
return;
}
if (error != null && !error.isEmpty()) {
display(() -> Component.literal("Could not fetch event rewards").withStyle(ChatFormatting.RED));
return;
}
display(() -> DiscordChatFormatter.systemLine("Pending event rewards: " + pendingLe + " LE", ChatFormatting.GOLD));
}

@Override
public void onAuthStatus(boolean linked, boolean member, String guildRank, String discordRank) {
handleAuthStatus(linked, member, guildRank, discordRank);
Expand Down Expand Up @@ -1625,6 +1684,39 @@ private void requestAllyView(FabricClientCommandSource source) {
}, 5, TimeUnit.SECONDS);
}

private LiteralArgumentBuilder<FabricClientCommandSource> buildEventCommand() {
return ClientCommandManager.literal("event").executes(ctx -> openLongEventGui(ctx.getSource())).then(ClientCommandManager.literal("rewards").executes(ctx -> {
requestEventRewards(ctx.getSource());
return 1;
}));
}

private int openLongEventGui(FabricClientCommandSource source) {
BridgeWebSocketClient current = socket;
if (current != null) {
current.sendLongEventStateRequest();
}
Minecraft mc = Minecraft.getInstance();
mc.execute(() -> mc.setScreen(new LongEventScreen(mc.screen, this)));
return 1;
}

private void requestEventRewards(FabricClientCommandSource source) {
BridgeWebSocketClient current = socket;
if (current == null || !current.isConnected()) {
source.sendFeedback(Component.literal("Could not fetch event rewards").withStyle(ChatFormatting.RED));
return;
}
long id = eventRewardsRequestId.incrementAndGet();
eventRewardsPendingId.set(id);
current.sendLongEventRewardsRequest();
partyCommandExecutor.schedule(() -> {
if (eventRewardsPendingId.compareAndSet(id, 0)) {
display(() -> Component.literal("Could not fetch event rewards").withStyle(ChatFormatting.RED));
}
}, 5, TimeUnit.SECONDS);
}

private CompletableFuture<Suggestions> suggestMembers(CommandContext<FabricClientCommandSource> context, SuggestionsBuilder builder) {
String remaining = builder.getRemaining().toLowerCase(Locale.ROOT);
for (String name : guildRewards.memberNames()) {
Expand Down Expand Up @@ -1744,6 +1836,16 @@ private static boolean isOwnGameResult(String content) {
return name != null && content.contains(name);
}

private static final java.util.regex.Pattern OWN_EVENT_LINE = java.util.regex.Pattern.compile("\\A(\\S+\\s+)\\S+\\s+(completed|unlocked)\\b");

/** Reword your own event line: "⭐ Name completed …" becomes "⭐ You've completed …". */
public static String formatOwnEventAnnouncement(String content) {
if (content == null) {
return null;
}
return OWN_EVENT_LINE.matcher(content).replaceFirst("$1You've $2");
}

private int openOverviewGui(FabricClientCommandSource source) {
Minecraft mc = Minecraft.getInstance();
mc.execute(this::openMenuGui);
Expand Down Expand Up @@ -2442,7 +2544,7 @@ private int handleReplyCommand(String messageId) {
private record HelpEntry(String command, String description) {
}

private static final List<HelpEntry> HELP_ENTRIES = List.of(new HelpEntry("/eden config", "open the config screen"), new HelpEntry("/eden online", "who's connected to the bridge"), new HelpEntry("/eden mail <player> <message>", "leave a member a message for their next login"), new HelpEntry("/eden cf", "flip a coin"), new HelpEntry("/eden diceroll", "roll a die"), new HelpEntry("/eden wars [days]", "guild war counts (same as Discord)"), new HelpEntry("/eden emojis", "open the chat emote picker"), new HelpEntry("/eden party", "list open parties (click to join)"), new HelpEntry("/eden party create <raid> [note]", "open a raid party"), new HelpEntry("/eden party join <id>", "join a party"), new HelpEntry("/eden party leave [id]", "leave your party"), new HelpEntry("/eden party disband", "disband your hosted party"), new HelpEntry("/eden anni <size> [note]", "open an Annihilation party (2-10)"), new HelpEntry("/eden command alias", "open the command alias editor"), new HelpEntry("/eden command keybind", "open the command keybind editor"), new HelpEntry("/eden update", "check for a pending update"), new HelpEntry("/eden update download", "download the update now (applies on exit)"), new HelpEntry("/eden aspects pending", "members' pending aspects — Chiefs only"), new HelpEntry("/eden gift <member> <aspect|emerald|tome> <amount>", "gift guild rewards — Chiefs only"), new HelpEntry("/eden dump <member>", "gift all guild-bank emeralds to a member — Chiefs only"), new HelpEntry("/eden deduct <aspects|emeralds> <member> <amount>", "deduct a payout from pending rewards — Chiefs only"), new HelpEntry("/eden ally view", "view guild ally activity (most inactive to active)"), new HelpEntry("/eden ally add <name>", "add a guild ally — Strategist+ only"), new HelpEntry("/eden ally remove <name>", "remove a guild ally — Strategist+ only"), new HelpEntry("/eden rewards", "check your pending aspects/emeralds and opt-out status"), new HelpEntry("/eden aspects optout [true|false]", "opt out of or into receiving aspects"), new HelpEntry("/eden emeralds optout [true|false]", "opt out of or into receiving emeralds"), new HelpEntry("/eden help", "this help screen"));
private static final List<HelpEntry> HELP_ENTRIES = List.of(new HelpEntry("/eden config", "open the config screen"), new HelpEntry("/eden online", "who's connected to the bridge"), new HelpEntry("/eden mail <player> <message>", "leave a member a message for their next login"), new HelpEntry("/eden cf", "flip a coin"), new HelpEntry("/eden diceroll", "roll a die"), new HelpEntry("/eden wars [days]", "guild war counts (same as Discord)"), new HelpEntry("/eden emojis", "open the chat emote picker"), new HelpEntry("/eden event", "open the event missions screen"), new HelpEntry("/eden event rewards", "check your pending event LE"), new HelpEntry("/eden party", "list open parties (click to join)"), new HelpEntry("/eden party create <raid> [note]", "open a raid party"), new HelpEntry("/eden party join <id>", "join a party"), new HelpEntry("/eden party leave [id]", "leave your party"), new HelpEntry("/eden party disband", "disband your hosted party"), new HelpEntry("/eden anni <size> [note]", "open an Annihilation party (2-10)"), new HelpEntry("/eden command alias", "open the command alias editor"), new HelpEntry("/eden command keybind", "open the command keybind editor"), new HelpEntry("/eden update", "check for a pending update"), new HelpEntry("/eden update download", "download the update now (applies on exit)"), new HelpEntry("/eden aspects pending", "members' pending aspects — Chiefs only"), new HelpEntry("/eden gift <member> <aspect|emerald|tome> <amount>", "gift guild rewards — Chiefs only"), new HelpEntry("/eden dump <member>", "gift all guild-bank emeralds to a member — Chiefs only"), new HelpEntry("/eden deduct <aspects|emeralds> <member> <amount>", "deduct a payout from pending rewards — Chiefs only"), new HelpEntry("/eden ally view", "view guild ally activity (most inactive to active)"), new HelpEntry("/eden ally add <name>", "add a guild ally — Strategist+ only"), new HelpEntry("/eden ally remove <name>", "remove a guild ally — Strategist+ only"), new HelpEntry("/eden rewards", "check your pending aspects/emeralds and opt-out status"), new HelpEntry("/eden aspects optout [true|false]", "opt out of or into receiving aspects"), new HelpEntry("/eden emeralds optout [true|false]", "opt out of or into receiving emeralds"), new HelpEntry("/eden help", "this help screen"));

private static final class TrackedCommandKeybind {
private final String input;
Expand Down Expand Up @@ -2637,6 +2739,11 @@ private synchronized void disconnect() {
tel.eden.mod.war.eco.TerritoryGuideController.reset();
guildAllyManager.requestCancel();
territoryDefendedTracker.clear();
eventRewardsPendingId.set(0);
knownLongEventState = null;
longEventGeneration.set(0);
longEventError = "";
longEventErrorAtMs = 0L;
// Before the socket goes: an outstanding deduct must not outlive the session that
// made it, or it resurfaces at the next connect naming a player from another
// server — or, after an account switch, another guild.
Expand Down Expand Up @@ -2986,7 +3093,7 @@ private void display(java.util.function.Supplier<Component> builder, boolean via
client.execute(() -> {
if (client.player != null) {
Component component = builder.get();
if (!viaChatTab || !WynntilsChatBridge.sendToTab(component)) {
if (component != null && (!viaChatTab || !WynntilsChatBridge.sendToTab(component))) {
client.player.displayClientMessage(component, false);
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/tel/eden/mod/config/BridgeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ private static Path resolveConfigPath() {
*/
public boolean partyAnnounce = true;

/**
* Whether other members' event announcements (Weekly, Master) are displayed in chat.
* When false, only your own event completions are shown. On by default.
*/
public boolean eventAnnouncements = true;

public enum GameDisplayMode {
ALL("Shown (All)"), NONE("Hidden (All)"), REACTIONS("Show Only Reactions");

Expand Down
1 change: 1 addition & 0 deletions src/tel/eden/mod/gui/BridgeConfigScreen.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ protected void init() {
addToggleRow(Category.GENERAL, "My login/logout messages", () -> config.announceSelfPresence, v -> config.announceSelfPresence = v, "On", "Off", true);
addToggleRow(Category.GENERAL, "Mail sound", () -> config.mailSound, v -> config.mailSound = v, "On", "Off", true);
addToggleRow(Category.GENERAL, "Party feed", () -> config.partyAnnounce, v -> config.partyAnnounce = v, "On", "Off", true);
addToggleRow(Category.GENERAL, "Event announcements", () -> config.eventAnnouncements, v -> config.eventAnnouncements = v, "On", "Off", true);
addToggleRow(Category.GENERAL, "Auto /stream on join", () -> config.autoStream, v -> config.autoStream = v, "On", "Off", false);
addToggleRow(Category.GENERAL, "Click shouts to reply", () -> config.shoutsClickable, v -> config.shoutsClickable = v, "On", "Off", true);
addToggleRow(Category.GENERAL, "Click-to-congratulate", () -> config.clickToCongratulate, v -> config.clickToCongratulate = v, "On", "Off", false);
Expand Down
Loading
Loading