diff --git a/mod.hjson b/mod.hjson index eb9d79ea..46e2de02 100644 --- a/mod.hjson +++ b/mod.hjson @@ -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 diff --git a/src/mindustrytool/IconUtils.java b/src/mindustrytool/IconUtils.java new file mode 100644 index 00000000..4ea098f1 --- /dev/null +++ b/src/mindustrytool/IconUtils.java @@ -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 iconcs = getIconc(); + + @Data + @RequiredArgsConstructor + public static class IconC { + private final String name; + private final Character value; + } + + private static Seq 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); + } +} diff --git a/src/mindustrytool/Main.java b/src/mindustrytool/Main.java index b3dc2fdc..35ecc4ba 100644 --- a/src/mindustrytool/Main.java +++ b/src/mindustrytool/Main.java @@ -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; diff --git a/src/mindustrytool/Utils.java b/src/mindustrytool/Utils.java index 199be17e..3ee2fe3b 100644 --- a/src/mindustrytool/Utils.java +++ b/src/mindustrytool/Utils.java @@ -42,6 +42,9 @@ public class Utils { public static LoadedMod mod; public static ObjectMap schematicData = new ObjectMap<>(); + private static ConcurrentHashMap iconCache = new ConcurrentHashMap<>(); + private static ConcurrentHashMap scalableIconCache = new ConcurrentHashMap<>(); + private static final byte[] header = { 'm', 's', 'c', 'h' }; private static final ObjectMapper mapper = new ObjectMapper() @@ -208,7 +211,9 @@ public static String getString(String text) { return text; } - private static ConcurrentHashMap 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)) { diff --git a/src/mindustrytool/features/FeatureMetadata.java b/src/mindustrytool/features/FeatureMetadata.java index 9294088c..655e5de7 100644 --- a/src/mindustrytool/features/FeatureMetadata.java +++ b/src/mindustrytool/features/FeatureMetadata.java @@ -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; @@ -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; } diff --git a/src/mindustrytool/features/autoplay/tasks/FleeTask.java b/src/mindustrytool/features/autoplay/tasks/FleeTask.java index bd12e723..a8fa6d51 100644 --- a/src/mindustrytool/features/autoplay/tasks/FleeTask.java +++ b/src/mindustrytool/features/autoplay/tasks/FleeTask.java @@ -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; @@ -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); } } } diff --git a/src/mindustrytool/features/browser/schematic/SchematicDialog.java b/src/mindustrytool/features/browser/schematic/SchematicDialog.java index ae05157c..dea2fd68 100644 --- a/src/mindustrytool/features/browser/schematic/SchematicDialog.java +++ b/src/mindustrytool/features/browser/schematic/SchematicDialog.java @@ -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); } }); }); diff --git a/src/mindustrytool/features/chat/global/ChatFeature.java b/src/mindustrytool/features/chat/global/ChatFeature.java index 127c5090..fbb514f7 100644 --- a/src/mindustrytool/features/chat/global/ChatFeature.java +++ b/src/mindustrytool/features/chat/global/ChatFeature.java @@ -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(() -> { @@ -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(() -> { @@ -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(() -> { @@ -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(() -> { diff --git a/src/mindustrytool/features/chat/global/ui/MessageList.java b/src/mindustrytool/features/chat/global/ui/MessageList.java index 3572a109..ed4f2bfe 100644 --- a/src/mindustrytool/features/chat/global/ui/MessageList.java +++ b/src/mindustrytool/features/chat/global/ui/MessageList.java @@ -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; @@ -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)); + }); }); - }); - 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)); @@ -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); card.clicked(() -> { if (expandedMessageId != null && expandedMessageId.equals(msg.id)) { @@ -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(); } } diff --git a/src/mindustrytool/features/chat/translation/ChatTranslationFeature.java b/src/mindustrytool/features/chat/translation/ChatTranslationFeature.java index e87d6a20..cd3a855c 100644 --- a/src/mindustrytool/features/chat/translation/ChatTranslationFeature.java +++ b/src/mindustrytool/features/chat/translation/ChatTranslationFeature.java @@ -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; @@ -31,7 +29,6 @@ public class ChatTranslationFeature implements Feature { private final Seq providers = new Seq<>(); private final TranslationProvider defaultTranslationProvider = new MindustryToolTranslationProvider(); - private final NoopTranslationProvider noopTranslationProvider = new NoopTranslationProvider(); private String lastError = null; private TranslationProvider currentProvider = defaultTranslationProvider; @@ -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()); @@ -94,8 +90,7 @@ public void handleMessage(String message, Cons 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; } @@ -163,9 +158,7 @@ public Optional setting() { card.clicked(() -> { if (currentProvider != prov) { currentProvider = prov; - var isNoop = prov.getId().equals(noopTranslationProvider.getId()); ChatTranslationConfig.setProviderId(prov.getId()); - FeatureManager.getInstance().setEnabled(this, !isNoop); } }); @@ -190,9 +183,6 @@ public Optional 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")); @@ -215,8 +205,8 @@ public Optional 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(); diff --git a/src/mindustrytool/features/chat/translation/NoopTranslationProvider.java b/src/mindustrytool/features/chat/translation/NoopTranslationProvider.java deleted file mode 100644 index 1040cb24..00000000 --- a/src/mindustrytool/features/chat/translation/NoopTranslationProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -package mindustrytool.features.chat.translation; - -import arc.Core; -import arc.scene.ui.layout.Table; - -import java.util.concurrent.CompletableFuture; - -public class NoopTranslationProvider implements TranslationProvider { - @Override - public CompletableFuture translate(String message) { - return CompletableFuture.completedFuture(message); - } - - @Override - public Table settings() { - return new Table(); - } - - @Override - public String getName() { - return Core.bundle.get("chat-translation.provider.none"); - } - - @Override - public String getId() { - return "noop"; - } -} diff --git a/src/mindustrytool/features/display/pathfinding/PathfindingCache.java b/src/mindustrytool/features/display/pathfinding/PathfindingCache.java index 07eca8fc..74119d18 100644 --- a/src/mindustrytool/features/display/pathfinding/PathfindingCache.java +++ b/src/mindustrytool/features/display/pathfinding/PathfindingCache.java @@ -1,5 +1,8 @@ package mindustrytool.features.display.pathfinding; +import lombok.Data; + +@Data public class PathfindingCache { public float[] data; public int size; diff --git a/src/mindustrytool/features/display/pathfinding/PathfindingCacheManager.java b/src/mindustrytool/features/display/pathfinding/PathfindingCacheManager.java new file mode 100644 index 00000000..932636b6 --- /dev/null +++ b/src/mindustrytool/features/display/pathfinding/PathfindingCacheManager.java @@ -0,0 +1,35 @@ +package mindustrytool.features.display.pathfinding; + +import arc.struct.LongMap; +import arc.struct.LongSeq; + +public class PathfindingCacheManager { + private final LongMap cache = new LongMap<>(); + private final LongSeq keysToRemove = new LongSeq(); + + public PathfindingCache get(long key) { + return cache.get(key); + } + + public void put(long key, PathfindingCache value) { + cache.put(key, value); + } + + public void clear() { + cache.clear(); + } + + public void cleanup(float currentTime, float maxAge) { + keysToRemove.clear(); + + for (LongMap.Entry entry : cache.entries()) { + if ((currentTime - entry.value.lastUsedTime) > maxAge) { + keysToRemove.add(entry.key); + } + } + + for (int i = 0; i < keysToRemove.size; i++) { + cache.remove(keysToRemove.get(i)); + } + } +} \ No newline at end of file diff --git a/src/mindustrytool/features/display/pathfinding/PathfindingDisplay.java b/src/mindustrytool/features/display/pathfinding/PathfindingDisplay.java index 2c324414..da8fb49b 100644 --- a/src/mindustrytool/features/display/pathfinding/PathfindingDisplay.java +++ b/src/mindustrytool/features/display/pathfinding/PathfindingDisplay.java @@ -8,48 +8,63 @@ import arc.math.Mathf; import arc.math.geom.Point2; import arc.math.geom.Rect; -import arc.scene.event.Touchable; import arc.scene.ui.Dialog; -import arc.scene.ui.Label; -import arc.scene.ui.Slider; -import arc.scene.ui.layout.Table; import arc.struct.IntSet; -import arc.struct.LongMap; -import arc.struct.LongSeq; -import arc.struct.Seq; +import arc.struct.IntSet.IntSetIterator; +import arc.util.Interval; +import arc.util.Log; import arc.util.Time; import arc.util.Tmp; import mindustry.Vars; import mindustry.ai.Pathfinder; -import mindustry.content.Blocks; import mindustry.game.Team; -import mindustry.game.EventType.TileOverlayChangeEvent; import mindustry.game.EventType.Trigger; import mindustry.game.EventType.WorldLoadEvent; import mindustry.gen.Groups; -import mindustry.gen.Icon; import mindustry.gen.Unit; import mindustry.graphics.Layer; -import mindustry.ui.Styles; -import mindustry.ui.dialogs.BaseDialog; import mindustry.world.Tile; import mindustrytool.Utils; import mindustrytool.features.Feature; import mindustrytool.features.FeatureMetadata; +import java.util.Arrays; import java.util.Optional; import static mindustry.Vars.*; public class PathfindingDisplay implements Feature { - private final LongMap pathCache = new LongMap<>(); - private final LongMap spawnPathCache = new LongMap<>(); - private final LongSeq keysToRemove = new LongSeq(); - private final IntSet activeTeams = new IntSet(); + private static final int MAX_STEPS_VERY_HIGH = 50; + private static final int MAX_STEPS_HIGH = 100; + private static final int MAX_STEPS_MEDIUM = 150; + private static final int MAX_STEPS_LOW = 250; + + private static final int MAX_UPDATES_PER_FRAME = 3; + private static final float CACHE_UPDATE_INTERVAL_UNIT = 15f; + private static final float CACHE_CLEANUP_AGE_UNIT = 60f; + private static final float CACHE_CLEANUP_AGE_SPAWN = 60f; + private static final float CACHE_UPDATE_INTERVAL_SPAWN = 60f; + + private static final int CULLING_THRESHOLD = 300; + private static final float CULLING_GROW = 500f; + + private static final int MAX_SPAWN_PATH_STEPS = 1000; + + private static final int TIMER_CLEANUP = 0; + private static final float CLEANUP_SCHEDULE_FRAMES = 60f; + + private final PathfindingCacheManager pathCache = new PathfindingCacheManager(); + private final PathfindingCacheManager spawnPathCache = new PathfindingCacheManager(); + + private final IntSet updateActiveTeams = new IntSet(); + private final IntSet drawActiveTeams = new IntSet(); + private final Interval timer = new Interval(1); - private Seq spawns = new Seq<>(false); private boolean isEnabled; - private BaseDialog settingsDialog; + private final PathfindingSettingsUI settingsUI = new PathfindingSettingsUI(); + + private int currentFrameUpdates; + private float currentOpacity; @Override public FeatureMetadata getMetadata() { @@ -67,28 +82,13 @@ public FeatureMetadata getMetadata() { public void init() { PathfindingConfig.load(); Events.run(Trigger.draw, this::draw); + Events.run(Trigger.update, this::update); Events.on(WorldLoadEvent.class, e -> reset()); - Events.on(TileOverlayChangeEvent.class, e -> { - if (e.previous == Blocks.spawn) { - spawns.remove(e.tile); - } - - if (e.overlay == Blocks.spawn) { - spawns.add(e.tile); - } - }); } public void reset() { - spawns.clear(); spawnPathCache.clear(); - - for (Tile tile : world.tiles) { - if (tile.overlay() == Blocks.spawn) { - spawns.add(tile); - } - } } @Override @@ -105,96 +105,26 @@ public void onDisable() { @Override public Optional setting() { - if (settingsDialog == null) { - settingsDialog = new BaseDialog("@pathfinding.settings.title"); - settingsDialog.name = "pathfindingSettingDialog"; - settingsDialog.addCloseButton(); - settingsDialog.shown(this::rebuildSettings); - settingsDialog.buttons.button("@reset", Icon.refresh, () -> { - PathfindingConfig.setZoomThreshold(0.5f); - rebuildSettings(); - }).size(250, 64); - } - return Optional.of(settingsDialog); + return Optional.of(settingsUI.getDialog()); } - private void rebuildSettings() { - Table settingsContainer = settingsDialog.cont; - settingsContainer.clear(); - settingsContainer.defaults().pad(6).left(); - - float width = Math.min(Core.graphics.getWidth() / 1.2f, 460f); - float currentZoom = PathfindingConfig.getZoomThreshold(); - - Slider zoomSlider = new Slider(0f, 5f, 0.1f, false); - zoomSlider.setValue(currentZoom); - - Label zoomValueLabel = new Label( - currentZoom <= 0.01f ? "@off" : String.format("%.1fx", currentZoom), - Styles.outlineLabel); - zoomValueLabel.setColor(currentZoom <= 0.01f ? Color.gray : Color.lightGray); - - Table zoomContent = new Table(); - zoomContent.touchable = Touchable.disabled; - zoomContent.margin(3f, 33f, 3f, 33f); - zoomContent.add("@health-bar.min-zoom", Styles.outlineLabel).left().growX(); - zoomContent.add(zoomValueLabel).padLeft(10f).right(); - - zoomSlider.changed(() -> { - float newZoomValue = zoomSlider.getValue(); - PathfindingConfig.setZoomThreshold(newZoomValue); - zoomValueLabel.setText(newZoomValue <= 0.01f ? "@off" : String.format("%.1fx", newZoomValue)); - zoomValueLabel.setColor(newZoomValue <= 0.01f ? Color.gray : Color.lightGray); - }); - - settingsContainer.stack(zoomSlider, zoomContent).width(width).left().padTop(4f).row(); - - Slider opacitySlider = new Slider(0f, 1f, 0.05f, false); - opacitySlider.setValue(PathfindingConfig.getOpacity()); - - Label opacityValue = new Label( - String.format("%.0f%%", PathfindingConfig.getOpacity() * 100), - Styles.outlineLabel); - opacityValue.setColor(Color.lightGray); - - Table opacityContent = new Table(); - opacityContent.touchable = Touchable.disabled; - opacityContent.margin(3f, 33f, 3f, 33f); - opacityContent.add("@opacity", Styles.outlineLabel).left().growX(); - opacityContent.add(opacityValue).padLeft(10f).right(); - - opacitySlider.changed(() -> { - PathfindingConfig.setOpacity(opacitySlider.getValue()); - opacityValue.setText(String.format("%.0f%%", PathfindingConfig.getOpacity() * 100)); - }); - - settingsContainer.stack(opacitySlider, opacityContent).width(width).left().padTop(4f).row(); - - settingsContainer.check("@pathfinding.draw-unit-path", PathfindingConfig.isDrawUnitPath(), (checked) -> { - PathfindingConfig.setDrawUnitPath(checked); - }).left().row(); + private void update() { + if (!isEnabled || !state.isGame()) { + return; + } - settingsContainer - .check("@pathfinding.draw-spawn-point-path", PathfindingConfig.isDrawSpawnPointPath(), (checked) -> { - PathfindingConfig.setDrawSpawnPointPath(checked); - rebuildSettings(); - }).left().row(); + if (timer.get(TIMER_CLEANUP, CLEANUP_SCHEDULE_FRAMES)) { + float time = Time.time; + pathCache.cleanup(time, CACHE_CLEANUP_AGE_UNIT); + spawnPathCache.cleanup(time, CACHE_CLEANUP_AGE_SPAWN); + } if (PathfindingConfig.isDrawSpawnPointPath()) { - Table costTable = new Table(); - costTable.left().defaults().left().padLeft(16); - - String[] costNames = { "@pathfinding.cost.ground", "@pathfinding.cost.legs", "@pathfinding.cost.water", - "@pathfinding.cost.neoplasm", "@pathfinding.cost.flat", "@pathfinding.cost.hover" }; - - for (int i = 0; i < costNames.length; i++) { - int index = i; - costTable.check(costNames[i], PathfindingConfig.isCostTypeEnabled(index), c -> { - PathfindingConfig.setCostTypeEnabled(index, c); - }).padBottom(4).row(); - } + updateSpawnPointPaths(); + } - settingsContainer.add(costTable).left().row(); + if (PathfindingConfig.isDrawUnitPath()) { + updateUnitPaths(); } } @@ -206,55 +136,43 @@ private void draw() { float zoomThreshold = PathfindingConfig.getZoomThreshold(); float currentZoom = renderer.getScale(); - if (-currentZoom > -zoomThreshold) { + if (currentZoom < zoomThreshold) { return; } + currentOpacity = PathfindingConfig.getOpacity(); + if (PathfindingConfig.isDrawSpawnPointPath()) { - drawSpawnPointPath(); + drawSpawnPointPaths(); } if (PathfindingConfig.isDrawUnitPath()) { - drawUnitPath(); + drawUnitPaths(); } } - private void drawUnitPath() { - Draw.z(Layer.overlayUI); - + private void updateUnitPaths() { int totalUnits = Groups.unit.size(); - int maxSteps = (totalUnits > 2000) ? 50 : (totalUnits > 1000) ? 100 : (totalUnits > 500) ? 150 : 250; - boolean useCulling = totalUnits > 300; - Rect cullBounds = useCulling ? Core.camera.bounds(Tmp.r1).grow(500f) : null; + int maxSteps = (totalUnits > 2000) ? MAX_STEPS_VERY_HIGH + : (totalUnits > 1000) ? MAX_STEPS_HIGH : (totalUnits > 500) ? MAX_STEPS_MEDIUM : MAX_STEPS_LOW; + + boolean useCulling = totalUnits > CULLING_THRESHOLD; + Rect cullBounds = useCulling ? Core.camera.bounds(Tmp.r1).grow(CULLING_GROW) : null; float currentTime = Time.time; - int[] updatesThisFrame = { 0 }; + currentFrameUpdates = 0; if (useCulling) { Groups.unit.intersect(cullBounds.x, cullBounds.y, cullBounds.width, cullBounds.height, unit -> { - processUnitPath(unit, maxSteps, currentTime, updatesThisFrame); + updateProcessUnitPath(unit, currentTime, maxSteps); }); } else { for (Unit unit : Groups.unit) { - processUnitPath(unit, maxSteps, currentTime, updatesThisFrame); - } - } - - if (Core.graphics.getFrameId() % 60 == 0) { - keysToRemove.clear(); - - for (LongMap.Entry entry : pathCache.entries()) { - if ((currentTime - entry.value.lastUsedTime) > 60f) { - keysToRemove.add(entry.key); - } - } - - for (int i = 0; i < keysToRemove.size; i++) { - pathCache.remove(keysToRemove.get(i)); + updateProcessUnitPath(unit, currentTime, maxSteps); } } } - private void processUnitPath(Unit unit, int maxSteps, float currentTime, int[] updatesThisFrame) { + private void updateProcessUnitPath(Unit unit, float currentTime, int maxSteps) { if (unit.team == player.team()) { return; } @@ -265,20 +183,58 @@ private void processUnitPath(Unit unit, int maxSteps, float currentTime, int[] u PathfindingCache cacheEntry = pathCache.get(cacheKey); - if (cacheEntry == null || (currentTime - cacheEntry.lastUpdateTime) > 15f) { + if (cacheEntry == null || (currentTime - cacheEntry.lastUpdateTime) > CACHE_UPDATE_INTERVAL_UNIT) { if (cacheEntry == null) { cacheEntry = new PathfindingCache(); - cacheEntry.data = new float[250 * 2]; + cacheEntry.data = new float[MAX_STEPS_LOW * 2]; pathCache.put(cacheKey, cacheEntry); } - if (updatesThisFrame[0] < 3) { + if (currentFrameUpdates < MAX_UPDATES_PER_FRAME) { cacheEntry.size = 0; recalculatePath(unit, cacheEntry, maxSteps); cacheEntry.lastUpdateTime = currentTime + Mathf.random(3f, 8f); - updatesThisFrame[0]++; + currentFrameUpdates++; } } + } + + private void drawUnitPaths() { + Draw.z(Layer.overlayUI); + + int totalUnits = Groups.unit.size(); + int maxSteps = (totalUnits > 2000) ? MAX_STEPS_VERY_HIGH + : (totalUnits > 1000) ? MAX_STEPS_HIGH : (totalUnits > 500) ? MAX_STEPS_MEDIUM : MAX_STEPS_LOW; + + boolean useCulling = totalUnits > CULLING_THRESHOLD; + Rect cullBounds = useCulling ? Core.camera.bounds(Tmp.r1).grow(CULLING_GROW) : null; + float currentTime = Time.time; + + if (useCulling) { + Groups.unit.intersect(cullBounds.x, cullBounds.y, cullBounds.width, cullBounds.height, unit -> { + drawProcessUnitPath(unit, currentTime, maxSteps); + }); + } else { + for (Unit unit : Groups.unit) { + drawProcessUnitPath(unit, currentTime, maxSteps); + } + } + } + + private void drawProcessUnitPath(Unit unit, float currentTime, int maxSteps) { + if (unit.team == player.team()) { + return; + } + + long packedPosition = Point2.pack(unit.tileX(), unit.tileY()); + long cacheKey = (((long) packedPosition) << 32) | ((long) unit.type.flowfieldPathType << 8) + | (long) unit.team.id; + + PathfindingCache cacheEntry = pathCache.get(cacheKey); + + if (cacheEntry == null) { + return; + } if (cacheEntry.lastUsedTime == currentTime) { return; @@ -320,10 +276,13 @@ private void recalculatePath(Unit unit, PathfindingCache cacheEntry, int maxStep for (int i = 0; i < maxSteps; i++) { Tile nextTile = pathfinder.getTargetTile(currentTile, field); - if (nextTile == null || nextTile == currentTile) + if (nextTile == null || nextTile == currentTile) { break; - if (dataIndex >= cacheEntry.data.length - 2) + } + + if (dataIndex >= cacheEntry.data.length - 2) { break; + } cacheEntry.data[dataIndex++] = nextTile.worldx(); cacheEntry.data[dataIndex++] = nextTile.worldy(); @@ -351,7 +310,7 @@ private void drawFromCache(PathfindingCache cacheEntry, Color pathColor, int max float nextX = cacheEntry.data[(i + 1) * 2]; float nextY = cacheEntry.data[(i + 1) * 2 + 1]; - Draw.color(pathColor, (1f - ((float) i / maxSteps)) * PathfindingConfig.getOpacity()); + Draw.color(pathColor, (1f - ((float) i / maxSteps)) * currentOpacity); Lines.line(currentX, currentY, nextX, nextY); currentX = nextX; @@ -360,25 +319,22 @@ private void drawFromCache(PathfindingCache cacheEntry, Color pathColor, int max Draw.reset(); } - private void drawSpawnPointPath() { + private void updateSpawnPointPaths() { if (pathfinder == null) { return; } - Draw.z(Layer.overlayUI); - float currentTime = Time.time; - // Collect unique enemy teams - activeTeams.clear(); + updateActiveTeams.clear(); for (var spawnPoint : Vars.state.rules.spawns) { var team = spawnPoint.team == null ? Vars.state.rules.waveTeam : spawnPoint.team; if (team != player.team()) { - activeTeams.add(team.id); + updateActiveTeams.add(team.id); } } - for (arc.struct.IntSet.IntSetIterator it = activeTeams.iterator(); it.hasNext;) { + for (IntSetIterator it = updateActiveTeams.iterator(); it.hasNext;) { int teamId = it.next(); Team team = Team.get(teamId); @@ -387,22 +343,61 @@ private void drawSpawnPointPath() { continue; } - for (var spawnTile : spawns) { + for (var spawnTile : Vars.spawner.getSpawns()) { long key = ((long) spawnTile.pos() << 32) | ((long) costType << 16) | (long) team.id; PathfindingCache cache = spawnPathCache.get(key); - if (cache == null || (currentTime - cache.lastUpdateTime) > 60f) { - if (cache == null) { - cache = new PathfindingCache(); - cache.data = new float[2048]; - spawnPathCache.put(key, cache); - } + if (cache == null) { + cache = new PathfindingCache(); + cache.data = new float[2048]; + spawnPathCache.put(key, cache); + } + + if ((currentTime - cache.lastUpdateTime) > CACHE_UPDATE_INTERVAL_SPAWN) { updateSpawnPathCache(cache, spawnTile, team, costType); cache.lastUpdateTime = currentTime + Mathf.random(0f, 20f); } + } + } + } + } + + private void drawSpawnPointPaths() { + if (pathfinder == null) { + return; + } + + Draw.z(Layer.overlayUI); + + float currentTime = Time.time; - if (cache.size > 0) { - drawSpawnPathFromCache(cache, team.color); + drawActiveTeams.clear(); + for (var spawnPoint : Vars.state.rules.spawns) { + var team = spawnPoint.team == null ? Vars.state.rules.waveTeam : spawnPoint.team; + if (team != player.team()) { + drawActiveTeams.add(team.id); + } + } + + for (IntSetIterator it = drawActiveTeams.iterator(); it.hasNext;) { + int teamId = it.next(); + Team team = Team.get(teamId); + + for (var costType = 0; costType < Pathfinder.costTypes.size; costType++) { + if (!PathfindingConfig.isCostTypeEnabled(costType)) { + continue; + } + + for (var spawnTile : Vars.spawner.getSpawns()) { + long key = ((long) spawnTile.pos() << 32) | ((long) costType << 16) | (long) team.id; + PathfindingCache cache = spawnPathCache.get(key); + + if (cache != null) { + cache.lastUsedTime = currentTime; + + if (cache.size > 0) { + drawSpawnPathFromCache(cache, team.color); + } } } } @@ -414,6 +409,7 @@ private void drawSpawnPointPath() { private void updateSpawnPathCache(PathfindingCache cache, Tile startTile, Team team, int costType) { int fieldType = Pathfinder.fieldCore; Pathfinder.Flowfield field = pathfinder.getField(team, costType, fieldType); + if (field == null) { cache.size = 0; return; @@ -422,18 +418,27 @@ private void updateSpawnPathCache(PathfindingCache cache, Tile startTile, Team t Tile currentTile = startTile; float segmentStartX = startTile.worldx(); float segmentStartY = startTile.worldy(); + int lastDx = -2, lastDy = -2; int dataIndex = 0; - if (dataIndex + 2 > cache.data.length) - cache.data = java.util.Arrays.copyOf(cache.data, cache.data.length * 2); + if (dataIndex + 2 > cache.data.length) { + cache.data = Arrays.copyOf(cache.data, cache.data.length * 2); + } + cache.data[dataIndex++] = segmentStartX; cache.data[dataIndex++] = segmentStartY; - for (int i = 0; i < 1000; i++) { + for (int i = 0; i < MAX_SPAWN_PATH_STEPS; i++) { Tile nextTile = pathfinder.getTargetTile(currentTile, field); - if (nextTile == null || nextTile == currentTile) + if (nextTile == null) { break; + } + + if (nextTile == currentTile) { + Log.err("Pathfinder loop detected"); + break; + } int dx = nextTile.x - currentTile.x; int dy = nextTile.y - currentTile.y; @@ -441,7 +446,7 @@ private void updateSpawnPathCache(PathfindingCache cache, Tile startTile, Team t if (dx != lastDx || dy != lastDy) { if (i > 0) { if (dataIndex + 2 > cache.data.length) - cache.data = java.util.Arrays.copyOf(cache.data, cache.data.length * 2); + cache.data = Arrays.copyOf(cache.data, cache.data.length * 2); cache.data[dataIndex++] = currentTile.worldx(); cache.data[dataIndex++] = currentTile.worldy(); } @@ -452,7 +457,7 @@ private void updateSpawnPathCache(PathfindingCache cache, Tile startTile, Team t } if (dataIndex + 2 > cache.data.length) - cache.data = java.util.Arrays.copyOf(cache.data, cache.data.length * 2); + cache.data = Arrays.copyOf(cache.data, cache.data.length * 2); cache.data[dataIndex++] = currentTile.worldx(); cache.data[dataIndex++] = currentTile.worldy(); @@ -460,9 +465,11 @@ private void updateSpawnPathCache(PathfindingCache cache, Tile startTile, Team t } private void drawSpawnPathFromCache(PathfindingCache cache, Color color) { - if (cache.size < 4) + if (cache.size < 4) { return; - Draw.color(color, PathfindingConfig.getOpacity()); + } + + Draw.color(color, currentOpacity); Lines.stroke(1f); for (int i = 0; i < cache.size - 2; i += 2) { diff --git a/src/mindustrytool/features/display/pathfinding/PathfindingSettingsUI.java b/src/mindustrytool/features/display/pathfinding/PathfindingSettingsUI.java new file mode 100644 index 00000000..8e9f4d18 --- /dev/null +++ b/src/mindustrytool/features/display/pathfinding/PathfindingSettingsUI.java @@ -0,0 +1,109 @@ +package mindustrytool.features.display.pathfinding; + +import arc.Core; +import arc.graphics.Color; +import arc.scene.event.Touchable; +import arc.scene.ui.Label; +import arc.scene.ui.Slider; +import arc.scene.ui.layout.Table; +import mindustry.gen.Icon; +import mindustry.ui.Styles; +import mindustry.ui.dialogs.BaseDialog; + +public class PathfindingSettingsUI { + private BaseDialog settingsDialog; + + public BaseDialog getDialog() { + if (settingsDialog == null) { + settingsDialog = new BaseDialog("@pathfinding.settings.title"); + settingsDialog.name = "pathfindingSettingDialog"; + settingsDialog.addCloseButton(); + settingsDialog.shown(this::rebuildSettings); + settingsDialog.buttons.button("@reset", Icon.refresh, () -> { + PathfindingConfig.setZoomThreshold(0.5f); + rebuildSettings(); + }).size(250, 64); + } + return settingsDialog; + } + + private void rebuildSettings() { + Table settingsContainer = settingsDialog.cont; + settingsContainer.clear(); + settingsContainer.defaults().pad(6).left(); + + float width = Math.min(Core.graphics.getWidth() / 1.2f, 460f); + float currentZoom = PathfindingConfig.getZoomThreshold(); + + Slider zoomSlider = new Slider(0f, 5f, 0.1f, false); + zoomSlider.setValue(currentZoom); + + Label zoomValueLabel = new Label( + currentZoom <= 0.01f ? "@off" : String.format("%.1fx", currentZoom), + Styles.outlineLabel); + zoomValueLabel.setColor(currentZoom <= 0.01f ? Color.gray : Color.lightGray); + + Table zoomContent = new Table(); + zoomContent.touchable = Touchable.disabled; + zoomContent.margin(3f, 33f, 3f, 33f); + zoomContent.add("@health-bar.min-zoom", Styles.outlineLabel).left().growX(); + zoomContent.add(zoomValueLabel).padLeft(10f).right(); + + zoomSlider.changed(() -> { + float newZoomValue = zoomSlider.getValue(); + PathfindingConfig.setZoomThreshold(newZoomValue); + zoomValueLabel.setText(newZoomValue <= 0.01f ? "@off" : String.format("%.1fx", newZoomValue)); + zoomValueLabel.setColor(newZoomValue <= 0.01f ? Color.gray : Color.lightGray); + }); + + settingsContainer.stack(zoomSlider, zoomContent).width(width).left().padTop(4f).row(); + + Slider opacitySlider = new Slider(0f, 1f, 0.05f, false); + opacitySlider.setValue(PathfindingConfig.getOpacity()); + + Label opacityValue = new Label( + String.format("%.0f%%", PathfindingConfig.getOpacity() * 100), + Styles.outlineLabel); + opacityValue.setColor(Color.lightGray); + + Table opacityContent = new Table(); + opacityContent.touchable = Touchable.disabled; + opacityContent.margin(3f, 33f, 3f, 33f); + opacityContent.add("@opacity", Styles.outlineLabel).left().growX(); + opacityContent.add(opacityValue).padLeft(10f).right(); + + opacitySlider.changed(() -> { + PathfindingConfig.setOpacity(opacitySlider.getValue()); + opacityValue.setText(String.format("%.0f%%", PathfindingConfig.getOpacity() * 100)); + }); + + settingsContainer.stack(opacitySlider, opacityContent).width(width).left().padTop(4f).row(); + + settingsContainer.check("@pathfinding.draw-unit-path", PathfindingConfig.isDrawUnitPath(), (checked) -> { + PathfindingConfig.setDrawUnitPath(checked); + }).left().row(); + + settingsContainer + .check("@pathfinding.draw-spawn-point-path", PathfindingConfig.isDrawSpawnPointPath(), (checked) -> { + PathfindingConfig.setDrawSpawnPointPath(checked); + rebuildSettings(); + }).left().row(); + + if (PathfindingConfig.isDrawSpawnPointPath()) { + Table costTable = new Table(); + costTable.left().defaults().left().padLeft(16); + + String[] costNames = { "@pathfinding.cost.ground", "@pathfinding.cost.legs", "@pathfinding.cost.water", + "@pathfinding.cost.neoplasm", "@pathfinding.cost.flat", "@pathfinding.cost.hover" }; + + for (int i = 0; i < costNames.length; i++) { + int index = i; + costTable.check(costNames[i], PathfindingConfig.isCostTypeEnabled(index), c -> { + PathfindingConfig.setCostTypeEnabled(index, c); + }).padBottom(4).row(); + } + + settingsContainer.add(costTable).left().row(); + } + } +} \ No newline at end of file diff --git a/src/mindustrytool/features/display/progress/ProgressDisplay.java b/src/mindustrytool/features/display/progress/ProgressDisplay.java index 2deeb7ea..148fa65d 100644 --- a/src/mindustrytool/features/display/progress/ProgressDisplay.java +++ b/src/mindustrytool/features/display/progress/ProgressDisplay.java @@ -23,7 +23,6 @@ import arc.scene.ui.Slider; import arc.scene.ui.layout.Table; import mindustry.gen.Icon; -import mindustry.gen.Tex; import mindustry.ui.Styles; import mindustry.ui.dialogs.BaseDialog; import mindustrytool.features.Feature; @@ -47,7 +46,7 @@ public FeatureMetadata getMetadata() { return FeatureMetadata.builder() .name("@feature.progress-display.name") .description("@feature.progress-display.description") - .icon(Tex.bar) + .icon(Icon.chartBar) .order(10) .enabledByDefault(true) .build(); diff --git a/src/mindustrytool/features/display/quickaccess/QuickAccessHud.java b/src/mindustrytool/features/display/quickaccess/QuickAccessHud.java index 549a66af..8bf1a310 100644 --- a/src/mindustrytool/features/display/quickaccess/QuickAccessHud.java +++ b/src/mindustrytool/features/display/quickaccess/QuickAccessHud.java @@ -28,6 +28,7 @@ import mindustry.ui.Styles; import mindustry.ui.dialogs.BaseDialog; import mindustrytool.Main; +import mindustrytool.Utils; import mindustrytool.features.Feature; import mindustrytool.features.FeatureManager; import mindustrytool.features.FeatureMetadata; @@ -161,7 +162,6 @@ private void populateContent(Table t) { btnRef[0] = t.button(b -> { b.image(meta.icon()) - .size(buttonSize * 0.7f) .scaling(Scaling.fit) .update(l -> l.setColor(f.isEnabled() ? Color.white : Pal.gray)); }, Styles.clearNonei, () -> { @@ -193,13 +193,10 @@ private void populateContent(Table t) { } Button[] btnRef = new Button[1]; - btnRef[0] = t.button(b -> { - b.image(Icon.settings) - .size(buttonSize * 0.7f) - .scaling(Scaling.fit); - }, Styles.clearNonei, () -> { - Main.featureSettingDialog.show(); - }) + btnRef[0] = t + .button(b -> b.image(Utils.scalable(Icon.settings)).scaling(Scaling.fit), Styles.clearNonei, () -> { + Main.featureSettingDialog.show(); + }) .size(buttonSize) .margin(margin) .get(); diff --git a/src/mindustrytool/features/display/togglerendering/ToggleRenderingFeature.java b/src/mindustrytool/features/display/togglerendering/ToggleRenderingFeature.java index 0729c549..a01026c2 100644 --- a/src/mindustrytool/features/display/togglerendering/ToggleRenderingFeature.java +++ b/src/mindustrytool/features/display/togglerendering/ToggleRenderingFeature.java @@ -33,7 +33,7 @@ public FeatureMetadata getMetadata() { return FeatureMetadata.builder() .name("@feature.toggle-rendering.name") .description("@feature.toggle-rendering.description") - .icon(Icon.eyeSmall) + .icon(Icon.eye) .order(5) .enabledByDefault(false) .quickAccess(true) diff --git a/src/mindustrytool/features/display/wavepreview/WavePreviewFeature.java b/src/mindustrytool/features/display/wavepreview/WavePreviewFeature.java index 37b03ce6..a7a6b318 100644 --- a/src/mindustrytool/features/display/wavepreview/WavePreviewFeature.java +++ b/src/mindustrytool/features/display/wavepreview/WavePreviewFeature.java @@ -34,7 +34,7 @@ public FeatureMetadata getMetadata() { return FeatureMetadata.builder() .name("@feature.wave-preview.name") .description("@feature.wave-preview.description") - .icon(Icon.units) + .icon( Icon.units) .order(1) .quickAccess(true) .enabledByDefault(true) diff --git a/src/mindustrytool/features/godmode/GodModeDialogs.java b/src/mindustrytool/features/godmode/GodModeDialogs.java index 715c0abe..58bb4085 100644 --- a/src/mindustrytool/features/godmode/GodModeDialogs.java +++ b/src/mindustrytool/features/godmode/GodModeDialogs.java @@ -16,6 +16,7 @@ import mindustry.ui.dialogs.BaseDialog; import mindustry.world.Block; import mindustry.world.blocks.storage.CoreBlock; +import mindustrytool.services.TapListener; import java.util.function.BiConsumer; import java.util.function.Consumer; diff --git a/src/mindustrytool/features/godmode/GodModeFeature.java b/src/mindustrytool/features/godmode/GodModeFeature.java index 29aeef17..e234c3b8 100644 --- a/src/mindustrytool/features/godmode/GodModeFeature.java +++ b/src/mindustrytool/features/godmode/GodModeFeature.java @@ -7,6 +7,7 @@ import arc.scene.ui.layout.Stack; import arc.scene.ui.layout.Table; import arc.util.Log; +import arc.util.Timer; import mindustry.Vars; import mindustry.game.EventType.PlayEvent; import mindustry.game.EventType.StateChangeEvent; @@ -43,13 +44,20 @@ public void init() { Events.run(PlayEvent.class, this::switchProvider); Events.run(StateChangeEvent.class, this::switchProvider); + Timer.schedule(this::switchProvider, 60, 60); } private void switchProvider() { + if (provider != null && provider.isAvailable()) { + return; + } + if (js.isAvailable()) { provider = js; } else if (internal.isAvailable()) { provider = internal; + } else { + provider = null; } rebuild(); diff --git a/src/mindustrytool/features/godmode/TapListener.java b/src/mindustrytool/features/godmode/TapListener.java deleted file mode 100644 index ecd323e7..00000000 --- a/src/mindustrytool/features/godmode/TapListener.java +++ /dev/null @@ -1,35 +0,0 @@ -package mindustrytool.features.godmode; - -import arc.Events; -import mindustry.game.EventType.TapEvent; - -import java.util.function.BiConsumer; - -public class TapListener { - private static final TapListener instance = new TapListener(); - - public static TapListener getInstance() { - return instance; - } - - private BiConsumer currentListener; - - public void init() { - Events.on(TapEvent.class, e -> { - if (currentListener != null) { - BiConsumer listener = currentListener; - - float worldX = e.tile.worldx(); - float worldY = e.tile.worldy(); - - currentListener = null; - - listener.accept(worldX, worldY); - } - }); - } - - public void select(BiConsumer onSelect) { - currentListener = onSelect; - } -} diff --git a/src/mindustrytool/features/playerconnect/PlayerConnect.java b/src/mindustrytool/features/playerconnect/PlayerConnect.java index 19bd5957..398f2414 100644 --- a/src/mindustrytool/features/playerconnect/PlayerConnect.java +++ b/src/mindustrytool/features/playerconnect/PlayerConnect.java @@ -60,6 +60,13 @@ public class PlayerConnect { updateStats(); }); + Timer.schedule(() -> { + if (isHosting() && Vars.net.client()) { + close(); + Vars.ui.showInfoFade("Auto close room when join another server"); + } + }, 1, 1); + Timer.schedule(() -> { updateStats(); }, 60f, 60f); diff --git a/src/mindustrytool/features/settings/FeatureCard.java b/src/mindustrytool/features/settings/FeatureCard.java index fb751be8..032da2f9 100644 --- a/src/mindustrytool/features/settings/FeatureCard.java +++ b/src/mindustrytool/features/settings/FeatureCard.java @@ -51,10 +51,15 @@ public void clicked(InputEvent event, float x, float y) { .ellipsis(true) .left(); + header.image(Utils.scalable(enabled ? Icon.eye : Icon.eyeOff)).height(24).width(32).padRight(8) + .color(enabled ? Color.white : Color.gray); + if (feature.setting().isPresent()) { - header.button(Icon.settings, Styles.clearNonei, + header.button(Utils.scalable(Icon.settings), Styles.clearNonei, () -> feature.setting().ifPresent(dialog -> Core.app.post(dialog::show))) - .size(32).padLeft(8).get().addListener(new ClickListener() { + .size(32) + .get() + .addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { event.stop(); @@ -62,8 +67,6 @@ public void clicked(InputEvent event, float x, float y) { }); } - header.image(enabled ? Icon.eyeSmall : Icon.eyeOffSmall).size(24).padLeft(4) - .color(enabled ? Color.white : Color.gray); }).growX().row(); c.add(Utils.getString(metadata.description())).color(Color.lightGray).fontScale(0.9f).wrap().growX() diff --git a/src/mindustrytool/features/settings/IconBrowserDialog.java b/src/mindustrytool/features/settings/IconBrowserDialog.java index 52fd2cff..a7df257b 100644 --- a/src/mindustrytool/features/settings/IconBrowserDialog.java +++ b/src/mindustrytool/features/settings/IconBrowserDialog.java @@ -3,11 +3,10 @@ import arc.Core; import arc.scene.ui.layout.Table; import arc.util.Align; -import arc.util.Log; import arc.util.Scaling; -import mindustry.gen.Iconc; import mindustry.ui.Styles; import mindustry.ui.dialogs.BaseDialog; +import mindustrytool.IconUtils; public class IconBrowserDialog extends BaseDialog { @@ -27,43 +26,30 @@ private void setup() { Runnable build = () -> { containers.clear(); - var declaredFields = Iconc.class.getDeclaredFields(); int col = 0; - for (var field : declaredFields) { - try { - field.setAccessible(true); - var icon = field.get(null); - - if (icon.equals(Iconc.all)) { - continue; - } - - if (icon instanceof String || icon instanceof Character) { - if (!field.getName().toLowerCase().contains(filter[0].toLowerCase())) { - continue; - } - - containers.button(String.valueOf(icon) + " " + field.getName(), () -> { - Core.app.setClipboardText(String.valueOf(icon)); - }) - .width(width) - .scaling(Scaling.fill) - .growX() - .padRight(8) - .padBottom(8) - .labelAlign(Align.left) - .top() - .left(); + for (var icon : IconUtils.iconcs) { + if (!icon.getName().toLowerCase().contains(filter[0].toLowerCase())) { + continue; + } - if (++col % cols == 0) { - containers.row(); - } - } - } catch (Exception e) { - Log.err(e); + containers.button(String.valueOf(icon.getValue()) + " " + icon.getName(), () -> { + Core.app.setClipboardText(String.valueOf(icon.getValue())); + }) + .width(width) + .scaling(Scaling.fill) + .growX() + .padRight(8) + .padBottom(8) + .labelAlign(Align.left) + .top() + .left(); + + if (++col % cols == 0) { + containers.row(); } } + }; cont.field(filter[0], Styles.defaultField, (t) -> { diff --git a/src/mindustrytool/features/smartdrill/SmartDrillFeature.java b/src/mindustrytool/features/smartdrill/SmartDrillFeature.java index 4b471a87..0573c820 100644 --- a/src/mindustrytool/features/smartdrill/SmartDrillFeature.java +++ b/src/mindustrytool/features/smartdrill/SmartDrillFeature.java @@ -9,7 +9,6 @@ import arc.struct.Seq; import arc.util.Align; import arc.util.Scaling; -import arc.util.Time; import arc.util.Timer; import mindustry.Vars; import mindustry.content.Blocks; @@ -26,13 +25,11 @@ import mindustry.world.blocks.production.Drill; import mindustrytool.features.Feature; import mindustrytool.features.FeatureMetadata; +import mindustrytool.services.TapListener; import arc.scene.ui.Dialog; import java.util.Optional; public class SmartDrillFeature implements Feature { - private Tile lastTapTile; - private long lastTapTime; - private Table currentMenu; private Tile selectedTile; @@ -57,24 +54,23 @@ public static int getMaxTiles(Block drill) { @Override public void init() { - Events.on(TapEvent.class, e -> { - if (!isEnabled()) { + TapListener.getInstance().registerHoldListener(300, 10, null, (tile, data) -> { + if (!isEnabled() || tile == null || tile.build != null || tile.drop() == null) { return; } + if (currentMenu == null) { + handleHold(tile); + } + }); - if (e.tile == null) { + Events.on(TapEvent.class, e -> { + if (!isEnabled()) { return; } - if (e.tile == lastTapTile && Time.timeSinceMillis(lastTapTime) < 500) { - // Double tap detected - handleDoubleTap(e.tile); - } else if (currentMenu != null && e.tile != selectedTile) { + if (currentMenu != null && e.tile != selectedTile) { closeMenu(); } - - lastTapTile = e.tile; - lastTapTime = Time.millis(); }); Events.on(StateChangeEvent.class, e -> { @@ -89,7 +85,7 @@ public void onDisable() { closeMenu(); } - private void handleDoubleTap(Tile tile) { + private void handleHold(Tile tile) { Item drop = tile.drop(); if (drop != null) { showDirectionMenu(tile); @@ -110,7 +106,7 @@ private void showDirectionMenu(Tile tile) { closeMenu(); selectedTile = tile; - currentMenu = new Table(Styles.black6); + currentMenu = new Table(); currentMenu.visible(() -> Vars.ui.hudfrag != null && Vars.ui.hudfrag.shown); currentMenu.touchable = arc.scene.event.Touchable.enabled; @@ -128,17 +124,17 @@ private void showDirectionMenu(Tile tile) { // Up directionTable.add().size(48f); - directionTable.button(Icon.up, Styles.clearNonei, () -> showDrillMenu(tile, 1)).size(48f).pad(4); + directionTable.button(Icon.up, () -> showDrillMenu(tile, 1)).size(48f).pad(4); directionTable.add().size(48f).row(); // Left, Cancel, Right - directionTable.button(Icon.left, Styles.clearNonei, () -> showDrillMenu(tile, 2)).size(48f).pad(4); - directionTable.button(Icon.cancel, Styles.clearNonei, this::closeMenu).size(48f).pad(4); - directionTable.button(Icon.right, Styles.clearNonei, () -> showDrillMenu(tile, 0)).size(48f).pad(4).row(); + directionTable.button(Icon.left, () -> showDrillMenu(tile, 2)).size(48f).pad(4); + directionTable.button(Icon.cancel, this::closeMenu).size(48f).pad(4); + directionTable.button(Icon.right, () -> showDrillMenu(tile, 0)).size(48f).pad(4).row(); // Down directionTable.add().size(48f); - directionTable.button(Icon.down, Styles.clearNonei, () -> showDrillMenu(tile, 3)).size(48f).pad(4); + directionTable.button(Icon.down, () -> showDrillMenu(tile, 3)).size(48f).pad(4); directionTable.add().size(48f); currentMenu.add(directionTable); @@ -271,7 +267,7 @@ private void place2x2Drill(Tile tile, int direction, Block drill, Item drop) { bridgeTiles.sort(t -> t.dst2(outMostTile)); var output = bridgeTiles.first().nearby(dir.mul(3)); - if (output == null){ + if (output == null) { output = bridgeTiles.first(); } var outputBridge = output; diff --git a/src/mindustrytool/features/smartupgrade/SmartUpgradeFeature.java b/src/mindustrytool/features/smartupgrade/SmartUpgradeFeature.java index abf5cf9e..b7fc1c29 100644 --- a/src/mindustrytool/features/smartupgrade/SmartUpgradeFeature.java +++ b/src/mindustrytool/features/smartupgrade/SmartUpgradeFeature.java @@ -43,11 +43,11 @@ import mindustry.world.blocks.production.BeamDrill; import mindustrytool.features.Feature; import mindustrytool.features.FeatureMetadata; +import mindustrytool.services.TapListener; public class SmartUpgradeFeature implements Feature { private Table currentMenu; private Tile selectedTile; - private Tile lastClick; @Override public FeatureMetadata getMetadata() { @@ -61,40 +61,29 @@ public FeatureMetadata getMetadata() { @Override public void init() { - Events.on(TapEvent.class, e -> { - if (!isEnabled()) { + TapListener.getInstance().registerHoldListener(300, 10, null, (tile, data) -> { + if (!isEnabled() || tile == null) { return; } - - if (e.tile == null) { - return; + if (currentMenu == null) { + if (getGroup(tile.block()) != BlockGroup.NONE) { + showMenu(tile); + } } + }); - if (e.tile != lastClick && lastClick != null) { - lastClick = e.tile; - closeMenu(); + Events.on(TapEvent.class, e -> { + if (!isEnabled()) { return; } - lastClick = e.tile; - if (currentMenu != null) { - if (e.tile == selectedTile) { closeMenu(); return; } closeMenu(); - - if (getGroup(e.tile.block()) != BlockGroup.NONE) { - showMenu(e.tile); - } - return; - } - - if (getGroup(e.tile.block()) != BlockGroup.NONE) { - showMenu(e.tile); } }); diff --git a/src/mindustrytool/services/TapListener.java b/src/mindustrytool/services/TapListener.java new file mode 100644 index 00000000..61cc3e5c --- /dev/null +++ b/src/mindustrytool/services/TapListener.java @@ -0,0 +1,119 @@ +package mindustrytool.services; + +import arc.Core; +import arc.Events; +import arc.math.geom.Vec2; +import arc.struct.ObjectSet; +import arc.struct.Seq; +import arc.util.Time; +import mindustry.Vars; +import mindustry.game.EventType.TapEvent; +import mindustry.game.EventType.Trigger; +import mindustry.world.Tile; + +import java.util.function.BiConsumer; + +public class TapListener { + private static final TapListener instance = new TapListener(); + + public static TapListener getInstance() { + return instance; + } + + private BiConsumer currentListener; + + private Tile touchTile; + private long touchTime; + private boolean wasTouched; + private final ObjectSet> triggeredListeners = new ObjectSet<>(); + private final Seq> holdListeners = new Seq<>(); + + public static class HoldRegistration implements Comparable> { + public long duration; + public int order; + public T data; + public BiConsumer callback; + + public HoldRegistration(long duration, int order, T data, BiConsumer callback) { + this.duration = duration; + this.order = order; + this.data = data; + this.callback = callback; + } + + @Override + public int compareTo(HoldRegistration o) { + return Integer.compare(this.order, o.order); + } + } + + public void registerHoldListener(long duration, int order, T data, BiConsumer callback) { + holdListeners.add(new HoldRegistration<>(duration, order, data, callback)); + holdListeners.sort(); + } + + public void init() { + Events.on(TapEvent.class, e -> { + if (currentListener != null) { + BiConsumer listener = currentListener; + + float worldX = e.tile.worldx(); + float worldY = e.tile.worldy(); + + currentListener = null; + + listener.accept(worldX, worldY); + } + }); + + Events.run(Trigger.update, () -> { + if (Vars.state == null || Vars.state.isMenu() || Core.scene.hasMouse()) { + resetHold(); + return; + } + + if (Core.input.isTouched()) { + Vec2 pos = Core.input.mouseWorld(); + Tile currentTile = Vars.world.tileWorld(pos.x, pos.y); + + if (!wasTouched) { + wasTouched = true; + touchTime = Time.millis(); + touchTile = currentTile; + triggeredListeners.clear(); + } else if (currentTile != touchTile) { + // Reset if dragged to a different tile + touchTile = currentTile; + touchTime = Time.millis(); + triggeredListeners.clear(); + } + + if (touchTile != null) { + long holdDuration = Time.timeSinceMillis(touchTime); + for (HoldRegistration listener : holdListeners) { + if (holdDuration >= listener.duration && !triggeredListeners.contains(listener)) { + triggeredListeners.add(listener); + invokeCallback(listener, touchTile); + } + } + } + } else { + resetHold(); + } + }); + } + + private void resetHold() { + wasTouched = false; + touchTile = null; + triggeredListeners.clear(); + } + + private void invokeCallback(HoldRegistration listener, Tile tile) { + listener.callback.accept(tile, listener.data); + } + + public void select(BiConsumer onSelect) { + currentListener = onSelect; + } +}