Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
65cb6a8
fix(godmode): handle case where no provider is available
sharrlotte Mar 30, 2026
ead4fc5
refactor(chat-translation): simplify translated message formatting
sharrlotte Mar 30, 2026
95bbfba
refactor(autoplay): update moveTo call with additional parameters
sharrlotte Mar 30, 2026
e23beae
fix(autoplay): increase flee task minimum distance from 0 to 40
sharrlotte Mar 30, 2026
fd1992a
fix(smartdrill): prevent action on tile with null build
sharrlotte Mar 30, 2026
c4628a8
refactor: replace double-tap detection with unified hold listener
sharrlotte Mar 30, 2026
41991b5
fix: catch Throwable instead of Exception for schematic copy error
sharrlotte Mar 30, 2026
d57b303
feat(godmode): schedule periodic provider switching
sharrlotte Mar 30, 2026
ebd4119
fix(godmode): prevent unnecessary provider switching
sharrlotte Mar 30, 2026
9a7a95a
refactor: move TapListener to services package for shared usage
sharrlotte Mar 30, 2026
29057ca
chore: update mod version to v4.51.1-v8
sharrlotte Mar 30, 2026
8e2a36a
feat(chat): condense consecutive messages from same user
sharrlotte Mar 30, 2026
247b9ec
style(ui): add padding to chat settings and simplify drill menu styling
sharrlotte Mar 30, 2026
def6d38
refactor(features): improve icon handling and fix spacing
sharrlotte Mar 30, 2026
1a366ff
fix(display): adjust toggle rendering icon and fix feature card layout
sharrlotte Mar 30, 2026
3c224ec
a
sharrlotte Mar 30, 2026
9e8ec9e
refactor(pathfinding): separate update and draw logic for performance
sharrlotte Mar 30, 2026
9723635
feat(playerconnect): auto close hosted room when joining another server
sharrlotte Mar 30, 2026
defeb8b
fix(IconBrowserDialog): correct icon filtering logic for string types
sharrlotte Mar 30, 2026
d972c0f
fix: prevent NPE when accessing static character icon fields
sharrlotte Mar 30, 2026
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 mod.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ description:
[#FAA31B[]]  Reddit: [white[]]r/MindustryTool[[]]
[#EF4444[]]  YouTube: [white[]]Mindustry Tool[[]]
'''
version: v4.51.0-v8
version: v4.51.2-v8

minGameVersion: 154

Expand Down
33 changes: 33 additions & 0 deletions src/mindustrytool/IconUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mindustrytool;

import arc.struct.Seq;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import mindustry.gen.Iconc;

public class IconUtils {

public static final Seq<IconC> iconcs = getIconc();

@Data
@RequiredArgsConstructor
public static class IconC {
private final String name;
private final Character value;
}

private static Seq<IconC> getIconc() {
return Seq.with(Iconc.class.getDeclaredFields())
.map(f -> {
try {
Object value = f.get(null);
if (value != null && value instanceof Character) {
return new IconC(f.getName(), (Character) value);
}
return null;
} catch (Exception e) {
return null;
}
}).select(f -> f != null);
}
}
2 changes: 1 addition & 1 deletion src/mindustrytool/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
import mindustrytool.features.smartupgrade.SmartUpgradeFeature;
import mindustrytool.features.smartdrill.SmartDrillFeature;
import mindustrytool.services.ServerService;
import mindustrytool.services.TapListener;
import mindustrytool.services.CrashReportService;
import mindustrytool.services.UpdateService;
import mindustrytool.features.chat.global.ChatFeature;
import mindustrytool.features.godmode.GodModeFeature;
import mindustrytool.features.godmode.TapListener;
import mindustrytool.features.autoplay.AutoplayFeature;
import mindustrytool.features.background.BackgroundFeature;
import mindustrytool.features.music.MusicFeature;
Expand Down
7 changes: 6 additions & 1 deletion src/mindustrytool/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public class Utils {
public static LoadedMod mod;

public static ObjectMap<String, Schematic> schematicData = new ObjectMap<>();
private static ConcurrentHashMap<String, TextureRegionDrawable> iconCache = new ConcurrentHashMap<>();
private static ConcurrentHashMap<TextureRegionDrawable, TextureRegionDrawable> scalableIconCache = new ConcurrentHashMap<>();

private static final byte[] header = { 'm', 's', 'c', 'h' };

private static final ObjectMapper mapper = new ObjectMapper()
Expand Down Expand Up @@ -208,7 +211,9 @@ public static String getString(String text) {
return text;
}

private static ConcurrentHashMap<String, TextureRegionDrawable> iconCache = new ConcurrentHashMap<>();
public static TextureRegionDrawable scalable(TextureRegionDrawable original) {
return scalableIconCache.computeIfAbsent(original, _key -> new TextureRegionDrawable(original.getRegion()));
}

public static TextureRegionDrawable icons(String name) {
if (iconCache.containsKey(name)) {
Expand Down
6 changes: 4 additions & 2 deletions src/mindustrytool/features/FeatureMetadata.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mindustrytool.features;

import arc.scene.style.Drawable;
import arc.scene.style.TextureRegionDrawable;
import mindustrytool.Utils;

public class FeatureMetadata {
String name;
Expand Down Expand Up @@ -66,8 +68,8 @@ public Builder description(String description) {
return this;
}

public Builder icon(Drawable icon) {
this.icon = icon;
public Builder icon(TextureRegionDrawable icon) {
this.icon = Utils.scalable(icon);
return this;
Comment on lines +71 to 73
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve the builder's null validation path.

icon(null) now fails inside Utils.scalable(...), so callers no longer get the explicit "Icon is required" error from build(). A null guard here keeps the builder behavior consistent.

💡 Suggested fix
-        public Builder icon(TextureRegionDrawable icon) {
-            this.icon = Utils.scalable(icon);
+        public Builder icon(TextureRegionDrawable icon) {
+            this.icon = icon == null ? null : Utils.scalable(icon);
             return this;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mindustrytool/features/FeatureMetadata.java` around lines 71 - 73, The
Builder.icon method currently calls Utils.scalable(icon) unconditionally which
breaks the builder's null validation path; change
Builder.icon(TextureRegionDrawable icon) to guard against null by not invoking
Utils.scalable when icon is null (i.e., set this.icon = null or leave unchanged)
so that null inputs still reach the Builder.build() null check and throw the
original "Icon is required" error; update the Builder.icon method (and any
related field) to preserve the original validation flow rather than performing
scaling eagerly in Utils.scalable.

}

Expand Down
5 changes: 3 additions & 2 deletions src/mindustrytool/features/autoplay/tasks/FleeTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public void setEnabled(boolean enabled) {
@Override
public boolean update(Unit unit) {
Unit enemy = Units.closestEnemy(unit.team, unit.x, unit.y, 300f, u -> !u.dead());
if (enemy != null && enemy.inRange(enemy)) {

if (enemy != null && enemy.inRange(unit)) {
ai.fleeFrom = enemy;
status = Core.bundle.get("autoplay.status.fleeing");
return true;
Expand All @@ -62,7 +63,7 @@ public static class FleeAI extends BaseAutoplayAI {
@Override
public void updateMovement() {
if (fleeFrom != null) {
moveTo(fleeFrom, fleeFrom.range() * 1.5f);
moveTo(fleeFrom, fleeFrom.range() * 1.5f, 40f, true, null);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,8 @@ public static void handleCopySchematic(String id) {
Schematic s = Utils.readSchematic(data);
Core.app.setClipboardText(Vars.schematics.writeBase64(s));
ui.showInfoFade("@copied");
} catch (Exception e) {
ui.showInfoFade(e.getMessage());
} catch (Throwable e) {
ui.showException(e);
Comment on lines +405 to +406
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether broad Throwable/Error catches exist elsewhere and need review.
rg -nP --type=java -C2 '\bcatch\s*\(\s*(Throwable|Error)\b'

Repository: MindustryTool/MindustryToolMod

Length of output: 1756


Narrow the catch type from Throwable to Exception.

Catching Throwable will intercept Error subclasses like OutOfMemoryError and StackOverflowError, which indicate fatal JVM states that should propagate. Since the operations in this block throw standard exceptions, narrowing to Exception preserves the improved error reporting while allowing fatal errors to propagate correctly.

Proposed fix
-                } catch (Throwable e) {
+                } catch (Exception e) {
                     ui.showException(e);
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (Throwable e) {
ui.showException(e);
} catch (Exception e) {
ui.showException(e);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mindustrytool/features/browser/schematic/SchematicDialog.java` around
lines 405 - 406, The catch block in SchematicDialog that currently reads "catch
(Throwable e)" should be narrowed to "catch (Exception e)" so fatal JVM Errors
(e.g., OutOfMemoryError, StackOverflowError) are not swallowed; locate the
try/catch containing ui.showException(e) in SchematicDialog.java and replace the
Throwable catch with Exception while keeping the existing ui.showException(e)
call and any surrounding finally/cleanup logic intact.

}
});
});
Expand Down
8 changes: 4 additions & 4 deletions src/mindustrytool/features/chat/global/ChatFeature.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private void rebuildSettings() {

Table opacityContent = new Table();
opacityContent.touchable = Touchable.disabled;
opacityContent.add("@opacity").left().growX();
opacityContent.add("@opacity").left().growX().padLeft(10).padRight(10);
opacityContent.add(opacityValue).padLeft(10f).right();

opacitySlider.changed(() -> {
Expand All @@ -164,7 +164,7 @@ private void rebuildSettings() {

Table scaleContent = new Table();
scaleContent.touchable = Touchable.disabled;
scaleContent.add("@scale").left().growX();
scaleContent.add("@scale").left().growX().padLeft(10).padRight(10);
scaleContent.add(scaleValue).padLeft(10f).right();

scaleSlider.changed(() -> {
Expand All @@ -183,7 +183,7 @@ private void rebuildSettings() {

Table widthContent = new Table();
widthContent.touchable = Touchable.disabled;
widthContent.add("@width").left().growX();
widthContent.add("@width").left().growX().padLeft(10).padRight(10);
widthContent.add(widthValue).padLeft(10f).right();

widthSlider.changed(() -> {
Expand All @@ -202,7 +202,7 @@ private void rebuildSettings() {

Table heightContent = new Table();
heightContent.touchable = Touchable.disabled;
heightContent.add("@height").left().growX();
heightContent.add("@height").left().growX().padLeft(10).padRight(10);
heightContent.add(heightValue).padLeft(10f).right();

heightSlider.changed(() -> {
Expand Down
105 changes: 59 additions & 46 deletions src/mindustrytool/features/chat/global/ui/MessageList.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -149,61 +150,71 @@ public void rebuild() {

float scale = ChatConfig.scale();

for (ChatMessage msg : channelMsgs) {
for (int i = 0; i < channelMsgs.size; i++) {
ChatMessage msg = channelMsgs.get(i);
boolean isSameUser = i > 0 && Objects.equals(channelMsgs.get(i - 1).createdBy, msg.createdBy);
boolean isNextSameUser = i < channelMsgs.size - 1
&& Objects.equals(channelMsgs.get(i + 1).createdBy, msg.createdBy);

Table entry = new Table();
entry.setBackground(null);

entry.table(avatar -> {
avatar.top();
UserService.findUserById(msg.createdBy).thenAccept(data -> {
Core.app.post(() -> {
avatar.clear();
if (data.getImageUrl() != null && !data.getImageUrl().isEmpty()) {
avatar.add(new NetworkImage(data.getImageUrl())).size(40 * scale);
} else {
avatar.add(new Image(Icon.players)).size(40 * scale);
}
if (!isSameUser) {
UserService.findUserById(msg.createdBy).thenAccept(data -> {
Core.app.post(() -> {
avatar.clear();
if (data.getImageUrl() != null && !data.getImageUrl().isEmpty()) {
avatar.add(new NetworkImage(data.getImageUrl())).size(40 * scale);
} else {
avatar.add(new Image(Icon.players)).size(40 * scale);
}
});
});
});
}).size(48 * scale).top().pad(8 * scale);
}
}).width(48 * scale).top().padLeft(8 * scale).padRight(8 * scale).padTop(isSameUser ? 0 : 8 * scale)
.padBottom(isNextSameUser ? 0 : 8 * scale);

entry.table(card -> {
card.top().left();

Label label = new Label("...");
label.setStyle(Styles.defaultLabel);
label.setFontScale(scale);

UserService.findUserById(msg.createdBy).thenAccept(data -> {
Core.app.post(() -> {
String timeStr = msg.createdAt;
if (msg.createdAt != null) {
try {
Instant instant = Instant.parse(msg.createdAt);
timeStr = DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneId.systemDefault())
.format(instant);
} catch (Throwable err) {
Log.err(err);
if (!isSameUser) {
Label label = new Label("...");
label.setStyle(Styles.defaultLabel);
label.setFontScale(scale);

UserService.findUserById(msg.createdBy).thenAccept(data -> {
Core.app.post(() -> {
String timeStr = msg.createdAt;
if (msg.createdAt != null) {
try {
Instant instant = Instant.parse(msg.createdAt);
timeStr = DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneId.systemDefault())
.format(instant);
} catch (Throwable err) {
Log.err(err);
}
}
}

Color color = data.getHighestRole()
.map(r -> {
try {
return Color.valueOf(r.getColor());
} catch (Exception err) {
return Color.white;
}
})
.orElse(Color.white);

label.setText("[#" + color.toString() + "]" + data.getName() + "[white]"
+ (timeStr.isEmpty() ? "" : " [gray]" + timeStr));

Color color = data.getHighestRole()
.map(r -> {
try {
return Color.valueOf(r.getColor());
} catch (Exception err) {
return Color.white;
}
})
.orElse(Color.white);

label.setText("[#" + color.toString() + "]" + data.getName() + "[white]"
+ (timeStr.isEmpty() ? "" : " [gray]" + timeStr));
Comment on lines +189 to +212
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard the nullable timestamp before calling isEmpty().

Line 190 already treats msg.createdAt as nullable, but Line 212 still dereferences timeStr. A null createdAt will throw here inside the UI callback.

🐛 Minimal fix
-                            String timeStr = msg.createdAt;
-                            if (msg.createdAt != null) {
+                            String timeStr = msg.createdAt == null ? "" : msg.createdAt;
+                            if (!timeStr.isEmpty()) {
                                 try {
                                     Instant instant = Instant.parse(msg.createdAt);
                                     timeStr = DateTimeFormatter.ofPattern("HH:mm")
                                             .withZone(ZoneId.systemDefault())
                                             .format(instant);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String timeStr = msg.createdAt;
if (msg.createdAt != null) {
try {
Instant instant = Instant.parse(msg.createdAt);
timeStr = DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneId.systemDefault())
.format(instant);
} catch (Throwable err) {
Log.err(err);
}
}
}
Color color = data.getHighestRole()
.map(r -> {
try {
return Color.valueOf(r.getColor());
} catch (Exception err) {
return Color.white;
}
})
.orElse(Color.white);
label.setText("[#" + color.toString() + "]" + data.getName() + "[white]"
+ (timeStr.isEmpty() ? "" : " [gray]" + timeStr));
Color color = data.getHighestRole()
.map(r -> {
try {
return Color.valueOf(r.getColor());
} catch (Exception err) {
return Color.white;
}
})
.orElse(Color.white);
label.setText("[#" + color.toString() + "]" + data.getName() + "[white]"
+ (timeStr.isEmpty() ? "" : " [gray]" + timeStr));
String timeStr = msg.createdAt == null ? "" : msg.createdAt;
if (!timeStr.isEmpty()) {
try {
Instant instant = Instant.parse(msg.createdAt);
timeStr = DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneId.systemDefault())
.format(instant);
} catch (Throwable err) {
Log.err(err);
}
}
Color color = data.getHighestRole()
.map(r -> {
try {
return Color.valueOf(r.getColor());
} catch (Exception err) {
return Color.white;
}
})
.orElse(Color.white);
label.setText("[#" + color.toString() + "]" + data.getName() + "[white]"
(timeStr.isEmpty() ? "" : " [gray]" + timeStr));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mindustrytool/features/chat/global/ui/MessageList.java` around lines 189
- 212, The label.setText call can NPE because timeStr may be null when
msg.createdAt is null; in MessageList.java (around the block that builds timeStr
and calls label.setText) ensure timeStr is non-null before calling isEmpty() —
either initialize timeStr to "" instead of msg.createdAt or change the
concatenation to check timeStr != null && !timeStr.isEmpty() before appending
the time. Update the code paths in the Instant.parse try/catch and the fallback
to guarantee timeStr is a safe (possibly empty) string used by label.setText.

});
});
});

card.add(label).left().row();
card.add(label).left().row();
}

if (msg.replyTo != null && !msg.replyTo.isEmpty()) {
ChatMessage repliedMsg = channelMsgs.find(m -> m.id.equals(msg.replyTo));
Expand All @@ -217,11 +228,12 @@ public void rebuild() {
replyContent.setColor(Color.gray);
replyContent.setEllipsis(true);
replyTable.add(replyContent).minWidth(0).maxWidth(200 * scale);
}).growX().padBottom(2 * scale).row();
}).growX().padTop(isSameUser ? 2 * scale : 0).padBottom(0).row();
}
}

card.table(c -> renderContent(c, msg.content, scale)).top().left().growX().padTop(6 * scale);
card.table(c -> renderContent(c, msg.content, scale)).top().left().growX()
.padTop(isSameUser && (msg.replyTo == null || msg.replyTo.isEmpty()) ? 2 * scale : 0);
Comment on lines +231 to +236
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Base the grouped top gap on whether a reply preview was actually rendered.

Line 231 only adds the reply row when the target message is present, but Line 236 keys off replyTo alone. If the replied message is outside the loaded window, this message loses the fallback isSameUser spacing and collapses into the previous bubble.

🩹 Possible fix
+                boolean hasRenderedReply = false;
                 if (msg.replyTo != null && !msg.replyTo.isEmpty()) {
                     ChatMessage repliedMsg = channelMsgs.find(m -> m.id.equals(msg.replyTo));
                     if (repliedMsg != null) {
+                        hasRenderedReply = true;
                         card.table(replyTable -> {
                             replyTable.center().left();
                             replyTable.image(Icon.rightSmall).size(16 * scale).padRight(4 * scale).color(Color.gray);
@@
                 }

                 card.table(c -> renderContent(c, msg.content, scale)).top().left().growX()
-                        .padTop(isSameUser && (msg.replyTo == null || msg.replyTo.isEmpty()) ? 2 * scale : 0);
+                        .padTop(isSameUser && !hasRenderedReply ? 2 * scale : 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mindustrytool/features/chat/global/ui/MessageList.java` around lines 231
- 236, The grouped-top gap is currently decided from msg.replyTo but the reply
preview row is only added when the target message exists, causing incorrect
spacing; modify MessageList rendering so you track whether the reply preview was
actually rendered (e.g., set a boolean like replyRendered when you add the reply
row in the block that calls card.table(...).row()) and then use that boolean
instead of msg.replyTo when computing padTop for the content card in
renderContent/card.table, ensuring padTop uses isSameUser && !replyRendered (or
the inverse as appropriate) to preserve fallback spacing when the reply target
is missing.


card.clicked(() -> {
if (expandedMessageId != null && expandedMessageId.equals(msg.id)) {
Expand Down Expand Up @@ -262,9 +274,10 @@ public void rebuild() {

}).growX().padTop(4 * scale);
}
}).growX().pad(8 * scale).top();
}).growX().padLeft(8 * scale).padRight(8 * scale).padTop(isSameUser ? 0 : 8 * scale)
.padBottom(isNextSameUser ? 0 : 8 * scale).top();

messageTable.add(entry).growX().padBottom(4 * scale).row();
messageTable.add(entry).growX().padBottom(isNextSameUser ? 0 : 4 * scale).row();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
import mindustry.gen.SendMessageCallPacket2;
import mindustry.ui.Styles;
import mindustry.ui.dialogs.BaseDialog;
import mindustry.ui.dialogs.LanguageDialog;
import mindustrytool.Main;
import mindustrytool.features.Feature;
import mindustrytool.features.FeatureManager;
import mindustrytool.features.FeatureMetadata;
import arc.struct.Seq;

Expand All @@ -31,7 +29,6 @@
public class ChatTranslationFeature implements Feature {
private final Seq<TranslationProvider> providers = new Seq<>();
private final TranslationProvider defaultTranslationProvider = new MindustryToolTranslationProvider();
private final NoopTranslationProvider noopTranslationProvider = new NoopTranslationProvider();
private String lastError = null;
private TranslationProvider currentProvider = defaultTranslationProvider;

Expand Down Expand Up @@ -74,7 +71,6 @@ public void init() {
Main.registerPacketPlacement(SendMessageCallPacket.class, SendTranslatedMessageCallPacket::new);
Main.registerPacketPlacement(SendMessageCallPacket2.class, SendTranslatedMessageCallPacket2::new);

providers.add(noopTranslationProvider);
providers.add(defaultTranslationProvider);
providers.add(new GeminiTranslationProvider());
providers.add(new DeepLTranslationProvider());
Expand All @@ -94,8 +90,7 @@ public void handleMessage(String message, Cons<String> cons) {
currentProvider.translate(Strings.stripColors(message))
.thenApply(translated -> {
if (ChatTranslationConfig.isShowOriginal()) {
String locale = LanguageDialog.getDisplayName(Core.bundle.getLocale());
String formated = Strings.format("[white]@[]\n\n[gold]@[]: @\n\n", message, locale, translated);
String formated = Strings.format("[white]@ [gray](@)", message, translated);

return formated;
}
Expand Down Expand Up @@ -163,9 +158,7 @@ public Optional<Dialog> setting() {
card.clicked(() -> {
if (currentProvider != prov) {
currentProvider = prov;
var isNoop = prov.getId().equals(noopTranslationProvider.getId());
ChatTranslationConfig.setProviderId(prov.getId());
FeatureManager.getInstance().setEnabled(this, !isNoop);
}
});

Expand All @@ -190,9 +183,6 @@ public Optional<Dialog> setting() {
TextButton testButton = new TextButton(Core.bundle.get("chat-translation.settings.test-button"),
Styles.defaultt);
testButton.clicked(() -> {
if (currentProvider == noopTranslationProvider)
return;

testButton.setDisabled(true);
testButton.setText(Core.bundle.get("chat-translation.settings.testing"));
resultLabel.setText(Core.bundle.get("chat-translation.settings.testing-connection"));
Expand All @@ -215,8 +205,8 @@ public Optional<Dialog> setting() {

root.add(testInput).growX().pad(10).row();
root.add(testButton).size(250, 50).pad(10)
.disabled(b -> currentProvider == noopTranslationProvider
|| testButton.getText().toString().equals(Core.bundle.get("chat-translation.settings.testing")))
.disabled(b -> testButton.getText().toString()
.equals(Core.bundle.get("chat-translation.settings.testing")))
.row();
root.add(resultLabel).growX().pad(10).row();

Expand Down
Loading
Loading