diff --git a/.gitignore b/.gitignore index eb693e8b..2931aeee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.idea /target /test -/out \ No newline at end of file +/out +dependency-reduced-pom.xml \ No newline at end of file diff --git a/DarkBOT.iml b/DarkBOT.iml index ca250368..ab686234 100644 --- a/DarkBOT.iml +++ b/DarkBOT.iml @@ -4,15 +4,30 @@ - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 0cbf8fde..633a596d 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,9 @@ How to run: - Add Run/Debug configuration: -> Main class: com.github.manolo8.darkbot.Bot -> Vm options: -Djava.library.path=./lib + +Distribution & support for the bot can be found over at discord: https://discord.gg/bEFgxCy + +Everyone is allowed to make, publish & redistribute videos & content about the software. + +Bugpoint is not affiliated in any way with this software. They claim themselves as the owners in DMCA claims, wich are all invalid. \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index 8f5bdab1..00000000 --- a/config.json +++ /dev/null @@ -1 +0,0 @@ -{"WORKING_MAP":92,"OFFENSIVE_CONFIG":1,"OFFENSIVE_FORMATION":"8","RUN_CONFIG":2,"RUN_FORMATION":"9","REFRESH_TIME":3600000,"CURRENT_MODULE":2,"MAX_DEATHS":10,"REPAIR_LOCAL":1,"REPAIR_HP":0.69,"WAIT_HP":0.9,"boxInfos":{"PIRATE_BOOTY":{"waitTime":0,"collect":false},"ore_1":{"waitTime":0,"collect":false},"MUCOSUM":{"waitTime":0,"collect":true},"BLACK_BOOTY_BOX":{"waitTime":0,"collect":false},"PRISMATIUM":{"waitTime":0,"collect":true},"FROM_SHIP":{"waitTime":0,"collect":false},"BONUS_BOX":{"waitTime":0,"collect":false},"MASQUE_BOOTY_BOX":{"waitTime":0,"collect":false},"BIFENON":{"waitTime":0,"collect":false}},"npcInfos":{"-\u003d[ Lordakium ]\u003d-":{"priority":0,"mapList":[25],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Streuner ]\u003d-":{"priority":0,"mapList":[-1],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Mordon ]\u003d-":{"priority":0,"mapList":[11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Recruit Streuner ]\u003d-":{"priority":0,"mapList":[-1],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Protegit ]\u003d-":{"priority":0,"mapList":[26],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Cubikon ]\u003d-":{"priority":0,"mapList":[26],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"( Uber Battleray )":{"priority":0,"mapList":[29],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"( Uber Saboteur )":{"priority":0,"mapList":[92],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Saimon ]\u003d-":{"priority":0,"mapList":[8,11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"..::{ Boss Lordakium }::...":{"priority":0,"mapList":[25],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"\\\\ Purpose XXI //":{"priority":0,"mapList":[28],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"\u003c\u003d\u003c Blighted Gygerthrall \u003e\u003d\u003e":{"priority":0,"mapList":[-1,27],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"..::{ Boss Sibelonit }::..":{"priority":0,"mapList":[25],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"\\\\ Attitude XIII //":{"priority":0,"mapList":[27],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"..::{ Boss Mordon }::..":{"priority":0,"mapList":[11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Kristallin ]\u003d-":{"priority":0,"mapList":[-1,26,27],"kill":true,"killOnlyIfIsLast":false,"radius":525.0},"\\\\ Observe X //":{"priority":0,"mapList":[27],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"( Uber Barracuda )":{"priority":0,"mapList":[92],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Blighted Kristallon ]\u003d-":{"priority":0,"mapList":[-1,25,27],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Devolarium ]\u003d-":{"priority":0,"mapList":[11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Lordakia ]\u003d-":{"priority":0,"mapList":[8,25,11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Saboteur ]\u003d-":{"priority":0,"mapList":[-1,91,92],"kill":true,"killOnlyIfIsLast":false,"radius":550.0},"..::{ Boss Kristallon }::..":{"priority":0,"mapList":[-1,25,27],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Interceptor ]\u003d-":{"priority":0,"mapList":[-1,91,92],"kill":true,"killOnlyIfIsLast":false,"radius":500.0},"..::{ Boss Devolarium }::..":{"priority":0,"mapList":[11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"..::{ Boss Kristallin }::..":{"priority":0,"mapList":[-1,26,27],"kill":true,"killOnlyIfIsLast":false,"radius":550.0},"-\u003d[ StreuneR ]\u003d-":{"priority":0,"mapList":[-1,28],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Barracuda ]\u003d-":{"priority":0,"mapList":[-1,91,92],"kill":true,"killOnlyIfIsLast":false,"radius":550.0},"( Uber Interceptor )":{"priority":0,"mapList":[92],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"..::{ Boss StreuneR }::..":{"priority":0,"mapList":[-1,28],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Kristallon ]\u003d-":{"priority":0,"mapList":[-1,26,27],"kill":true,"killOnlyIfIsLast":false,"radius":580.0},"-\u003d[ Sibelonit ]\u003d-":{"priority":0,"mapList":[25],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"..::{ Boss Saimon }::..":{"priority":0,"mapList":[11],"kill":true,"killOnlyIfIsLast":false,"radius":400.0},"( Uber Annihilator )":{"priority":0,"mapList":[92],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Annihilator ]\u003d-":{"priority":0,"mapList":[92],"kill":false,"killOnlyIfIsLast":false,"radius":400.0},"-\u003d[ Plagued Kristallin ]\u003d-":{"priority":0,"mapList":[26],"kill":false,"killOnlyIfIsLast":false,"radius":550.0}},"RUN_FROM_ENEMIES":true,"RUN_FROM_ENEMIES_IN_SIGHT":false,"AMMO_KEY":"3","AUTO_SAB":true,"AUTO_SAB_KEY":"4","STAY_AWAY_FROM_ENEMIES":false,"AUTO_CLOACK":false,"AUTO_CLOACK_KEY":"\u0000"} \ No newline at end of file diff --git a/lib/DarkBot.dll b/lib/DarkBot.dll index cc43cda4..fe447bd2 100644 Binary files a/lib/DarkBot.dll and b/lib/DarkBot.dll differ diff --git a/lib/darcula.jar b/lib/darcula.jar new file mode 100644 index 00000000..59e86b4b Binary files /dev/null and b/lib/darcula.jar differ diff --git a/lib/verifier.jar b/lib/verifier.jar new file mode 100644 index 00000000..5ac817e9 Binary files /dev/null and b/lib/verifier.jar differ diff --git a/pom.xml b/pom.xml index 4bee1c4e..da475930 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,18 @@ com.github.manolo8.darkbot DarkBOT - 0.7.4-SNAPSHOT + 1.13-SNAPSHOT + + + UTF-8 + + + + + jfrog + https://oss.jfrog.org/artifactory/oss-snapshot-local/com/formdev/ + + @@ -16,6 +27,60 @@ 2.8.5 + + com.formdev + flatlaf + 0.36 + + + + + + + + + + com.miglayout + miglayout + 3.7.4 + + + + org.jgrapht + jgrapht-core + 1.3.0 + + + + org.jgrapht + jgrapht-io + 1.3.0 + + + + org.mvel + mvel2 + 2.4.4.Final + + + + net.java.dev.jna + jna + 5.2.0 + + + + net.java.dev.jna + jna-platform + 5.2.0 + + + + org.jetbrains + annotations + 17.0.0 + compile + @@ -24,25 +89,57 @@ org.apache.maven.plugins maven-compiler-plugin + 3.8.1 8 8 + UTF-8 - maven-assembly-plugin + org.apache.maven.plugins + maven-shade-plugin + 3.1.1 - assembly + package - single + shade - package - - jar-with-dependencies - + DarkBot + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/LICENSE* + META-INF/NOTICE.txt + + + + net.java.dev.jna:* + + com/sun/jna/* + com/sun/jna/ptr/* + com/sun/jna/platform/win32/** + com/sun/jna/win32/** + com/sun/jna/win32-x86-64/** + + + **/*.html + **/*.template + + + + + + com.github.manolo8.darkbot.Bot + + @@ -51,68 +148,32 @@ com.github.wvengen proguard-maven-plugin - 2.0.13 + 2.1.1 - obfuscation-packaging package - - proguard - + proguard - 6.1.0beta2 - true - true - false - false - ${project.build.finalName}-jar-with-dependencies.jar - false - - - com.github.manolo8.darkbot.Bot - - - - ${java.home}/lib/rt.jar - - + DarkBot.jar + DarkBot.jar - - - - - - - - - - - - - - + + + + + + + + + + + ${java.home}/lib/rt.jar + - - - net.sf.proguard - proguard-base - 6.1.0beta2 - - diff --git a/replacements.txt b/replacements.txt new file mode 100644 index 00000000..13eb352e --- /dev/null +++ b/replacements.txt @@ -0,0 +1,5 @@ +//for v10.0.19071543, * means is a replacement or just add this in formater +_-UV=*Main* +_-P1v=*Screen Manager* +_-15e=*Gui Manager* +//didnt check for other names on that version. diff --git a/src/main/java/a.java b/src/main/java/a.java new file mode 100644 index 00000000..a6028243 --- /dev/null +++ b/src/main/java/a.java @@ -0,0 +1,3 @@ +public class a extends Exception { + int a; +} diff --git a/src/main/java/com/github/manolo8/darkbot/Bot.java b/src/main/java/com/github/manolo8/darkbot/Bot.java index d9360b93..b44efce6 100644 --- a/src/main/java/com/github/manolo8/darkbot/Bot.java +++ b/src/main/java/com/github/manolo8/darkbot/Bot.java @@ -1,9 +1,33 @@ package com.github.manolo8.darkbot; +import com.formdev.flatlaf.FlatDarkLaf; +import com.github.manolo8.darkbot.config.ConfigManager; +import com.github.manolo8.darkbot.utils.LogUtils; + +import javax.swing.*; + public class Bot { public static void main(String[] args) { + if (System.console() == null + && Bot.class.getProtectionDomain().getCodeSource().getLocation().getPath().endsWith(".jar")) { + LogUtils.setOutputToFile(); + } + try { + UIManager.getFont("Label.font"); // Prevents a linux crash + UIManager.setLookAndFeel(new FlatDarkLaf()); + UIManager.put("Button.arc", 0); + UIManager.put("Component.arc", 0); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + SwingUtilities.invokeLater(Main::new); + } - new Main(); + public static class NoOpBot { + public static void main(String[] args) { + ConfigManager.FORCE_NO_OP = true; + Bot.main(args); + } } } diff --git a/src/main/java/com/github/manolo8/darkbot/Main.java b/src/main/java/com/github/manolo8/darkbot/Main.java index 2ddbdfb5..25c5d356 100644 --- a/src/main/java/com/github/manolo8/darkbot/Main.java +++ b/src/main/java/com/github/manolo8/darkbot/Main.java @@ -1,257 +1,290 @@ package com.github.manolo8.darkbot; +import com.github.manolo8.darkbot.backpage.BackpageManager; import com.github.manolo8.darkbot.config.Config; -import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.ConfigManager; +import com.github.manolo8.darkbot.config.utils.ByteArrayToBase64TypeAdapter; +import com.github.manolo8.darkbot.config.utils.SpecialTypeAdapter; import com.github.manolo8.darkbot.core.BotInstaller; -import com.github.manolo8.darkbot.core.DarkBotAPI; +import com.github.manolo8.darkbot.core.IDarkBotAPI; +import com.github.manolo8.darkbot.core.itf.Behaviour; +import com.github.manolo8.darkbot.core.itf.Configurable; import com.github.manolo8.darkbot.core.itf.Module; import com.github.manolo8.darkbot.core.manager.*; import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.features.FeatureRegistry; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.extensions.plugins.PluginHandler; +import com.github.manolo8.darkbot.extensions.plugins.PluginListener; +import com.github.manolo8.darkbot.extensions.util.VerifierChecker; +import com.github.manolo8.darkbot.extensions.util.Version; import com.github.manolo8.darkbot.gui.MainGui; -import com.github.manolo8.darkbot.modules.CollectorModule; -import com.github.manolo8.darkbot.modules.LootModule; -import com.github.manolo8.darkbot.modules.LootNCollectorModule; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.modules.DisconnectModule; +import com.github.manolo8.darkbot.modules.DummyModule; +import com.github.manolo8.darkbot.modules.TemporalModule; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.Time; +import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; +import javax.swing.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; -public class Main extends Thread { +public class Main extends Thread implements PluginListener { + public static final Version VERSION = new Version("1.13.17 beta 37"); public static final Object UPDATE_LOCKER = new Object(); + public static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() + .setLenient() + .registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()) + .registerTypeAdapterFactory(new SpecialTypeAdapter()) + .create(); + + public ConfigManager configManager = new ConfigManager(); + public Config config = configManager.loadConfig(null); + public static IDarkBotAPI API; + + public final Lazy.Sync status = new Lazy.Sync<>(); + public final Lazy.Sync configChange = new Lazy.Sync<>(); + public final StarManager starManager = new StarManager(); + public final MapManager mapManager = new MapManager(this); + public final SettingsManager settingsManager = new SettingsManager(this); + public final HeroManager hero = new HeroManager(this); + public final FacadeManager facadeManager = new FacadeManager(this); + public final EffectManager effectManager = new EffectManager(this); + public final GuiManager guiManager = new GuiManager(this); + public final StatsManager statsManager = new StatsManager(this); + public final PingManager pingManager = new PingManager(); + public final BackpageManager backpage = new BackpageManager(this); + public final PluginHandler pluginHandler = new PluginHandler(); + public final FeatureRegistry featureRegistry = new FeatureRegistry(this, pluginHandler); + public final RepairManager repairManager = new RepairManager(); - public static DarkBotAPI API; - - public final MapManager mapManager; - public final StarManager starManager; - public final HeroManager hero; - public final GuiManager guiManager; - public final StatsManager statsManager; - public final PingManager pingManager; - - public final Lazy status; + private final MainGui form; + private final BotInstaller botInstaller = new BotInstaller( + settingsManager, facadeManager, effectManager, guiManager, mapManager, hero, statsManager, pingManager, repairManager); - public Config config; public Module module; + public long lastRefresh = System.currentTimeMillis(); + public double avgTick; + public boolean tickingModule; - private long lastRefresh; - - private final BotInstaller botInstaller; - private final MainGui form; + private String moduleId; + private List behaviours = new ArrayList<>(); private volatile boolean running; public Main() { - API = new DarkBotAPI(); - API.createWindow(); - - this.config = new Config(); - - loadConfig(); - - new ConfigEntity(config); - - botInstaller = new BotInstaller(); - status = new Lazy<>(); + super("Main"); + VerifierChecker.getAuthApi().setupAuth(); + API = configManager.getAPI(); + API.setSize(config.BOT_SETTINGS.DISPLAY.width, config.BOT_SETTINGS.DISPLAY.height); - guiManager = new GuiManager(this); - starManager = new StarManager(); - mapManager = new MapManager(this); - hero = new HeroManager(this); - statsManager = new StatsManager(this); - pingManager = new PingManager(); - - botInstaller.add(guiManager); - botInstaller.add(mapManager); - botInstaller.add(hero); - botInstaller.add(statsManager); - botInstaller.add(pingManager); - - botInstaller.init(); - - botInstaller.invalid.add(value -> { + this.botInstaller.invalid.add(value -> { if (!value) lastRefresh = System.currentTimeMillis(); }); - updateConfig(); + this.status.add(this::onRunningToggle); + this.configChange.add(this::setConfig); + + this.pluginHandler.updatePluginsSync(); + this.pluginHandler.addListener(this); + + this.form = new MainGui(this); - form = new MainGui(this); + if (configManager.getConfigFailed()) + Popups.showMessageAsync("Error", I18n.get("bot.issue.config_load_failed"), JOptionPane.ERROR_MESSAGE); + API.createWindow(); start(); } @Override + @SuppressWarnings("InfiniteLoopStatement") public void run() { long time; while (true) { time = System.currentTimeMillis(); - tick(); + try { + tick(); + } catch (Exception e) { + e.printStackTrace(); + Time.sleep(1000); + } - sleepMax(time, 100); + avgTick = ((avgTick * 9) + (System.currentTimeMillis() - time)) / 10; + + Time.sleepMax(time, botInstaller.invalid.get() ? 1000 : + Math.max(config.BOT_SETTINGS.MIN_TICK, Math.min((int) (avgTick * 1.25), 100))); } } private void tick() { + this.status.tick(); + checkModule(); - if (isInvalid()) - invalidTick(); - else - validTick(); - - pingManager.tick(); - form.tick(); + if (isInvalid()) tickingModule = false; + else validTick(); - checkConfig(); + this.form.tick(); + this.configManager.saveChangedConfig(); + this.configChange.tick(); } private boolean isInvalid() { return botInstaller.isInvalid(); } - private void invalidTick() { - botInstaller.verify(); - } - private void validTick() { - - guiManager.tick(); + settingsManager.tick(); hero.tick(); mapManager.tick(); + facadeManager.tick(); + effectManager.tick(); + guiManager.tick(); + statsManager.tick(); + repairManager.tick(); - if (running && guiManager.canTickModule()) - tickRunning(); - } + tickingModule = running && guiManager.canTickModule(); + if (tickingModule) tickRunning(); + else tickLogic(false); - private void tickRunning() { - module.tick(); - statsManager.tick(); + if (!running && (!hero.hasTarget() || !mapManager.isTarget(hero.target))) { + hero.setTarget(Stream.concat(mapManager.entities.ships.stream(), mapManager.entities.npcs.stream()) + .filter(mapManager::isTarget) + .findFirst().orElse(null)); + } - checkPetBug(); + pingManager.tick(); + } + private void tickRunning() { + guiManager.pet.tick(); + guiManager.group.tick(); checkRefresh(); + tickLogic(true); } - private void checkConfig() { - if (config.changed) { - config.changed = false; - saveConfig(); + private void tickLogic(boolean running) { + synchronized (pluginHandler) { + try { + if (running) module.tickModule(); + else module.tickStopped(); + } catch (Throwable e) { + FeatureDefinition modDef = featureRegistry.getFeatureDefinition(module); + if (modDef != null) modDef.getIssues().addWarning(I18n.get("bot.issue.feature.failed_to_tick"), IssueHandler.createDescription(e)); + } + for (Behaviour behaviour : behaviours) { + try { + if (running) behaviour.tickBehaviour(); + else behaviour.tickStopped(); + } catch (Throwable e) { + featureRegistry.getFeatureDefinition(behaviour) + .getIssues() + .addFailure(I18n.get("bot.issue.feature.failed_to_tick"), IssueHandler.createDescription(e)); + } + } } } - private void checkPetBug() { - if (mapManager.isTarget(hero.pet)) hero.pet.clickable.setRadius(0); - } - private void checkRefresh() { - if (config.REFRESH_TIME != 0) { - - boolean refreshTimer = System.currentTimeMillis() - lastRefresh > config.REFRESH_TIME; - boolean canRefresh = module.canRefresh(); - - if (refreshTimer && canRefresh) { - API.refresh(); - lastRefresh = System.currentTimeMillis(); - } + if (config.MISCELLANEOUS.REFRESH_TIME == 0 || + System.currentTimeMillis() - lastRefresh < config.MISCELLANEOUS.REFRESH_TIME * 60 * 1000) return; + + if (!module.canRefresh()) return; + lastRefresh = System.currentTimeMillis(); + if (config.MISCELLANEOUS.PAUSE_FOR > 0) { + System.out.println("Pausing (logging off): time arrived & module allows refresh"); + setModule(new DisconnectModule(config.MISCELLANEOUS.PAUSE_FOR * 60 * 1000L, I18n.get("module.disconnect.reason.break"))); + } else { + System.out.println("Triggering refresh: time arrived & module allows refresh"); + API.handleRefresh(); } } public A setModule(A module) { + return setModule(module, false); + } + + private A setModule(A module, boolean setConfig) { module.install(this); + if (setConfig) updateCustomConfig(module); this.module = module; return module; } - public void setRunning(boolean running) { - if (this.running != running) { - - status.send(running); - - this.running = running; + private void updateCustomConfig(A module) { + if (module instanceof Configurable) { + String name = module.getClass().getAnnotation(Feature.class).name(); + form.setCustomConfig(name, config.CUSTOM_CONFIGS.get(module.getClass().getCanonicalName())); + } else { + form.setCustomConfig(null, null); } } - private void loadConfig() { - try { - - File config = new File("config.json"); - - if (config.exists()) { - - FileReader reader = new FileReader(config); - - this.config = new GsonBuilder().create().fromJson(reader, Config.class); - - if (this.config == null) this.config = new Config(); - - reader.close(); - - } else { - saveConfig(); - } - - } catch (IOException e) { - e.printStackTrace(); - } + @Override + public void beforeLoad() { + if (module != null) setModule(new DummyModule(), true); } - public void saveConfig() { - try { - - File config = new File("config.json"); - - FileWriter writer = new FileWriter(config); - - new GsonBuilder().create().toJson(this.config, writer); - - writer.close(); - - } catch (IOException e) { - e.printStackTrace(); - } + @Override + public void afterLoadComplete() { + moduleId = "(none)"; } public boolean isRunning() { return running; } - private void updateConfig() { - switch (config.CURRENT_MODULE) { - case 0: - if (isNotModule(CollectorModule.class)) { - setModule(new CollectorModule()); - } - break; - case 1: - if (isNotModule(LootModule.class)) { - setModule(new LootModule()); - } - break; - case 2: - if (isNotModule(LootNCollectorModule.class)) { - setModule(new LootNCollectorModule()); - } - break; - default: - setModule(new CollectorModule()); - } + public void setRunning(boolean running) { + if (this.running == running) return; + status.send(running); + this.running = running; } - private boolean isNotModule(Class clazz) { - return module == null || module.getClass() != clazz; + private void onRunningToggle(boolean running) { + if (config.MISCELLANEOUS.RESET_REFRESH) + lastRefresh = System.currentTimeMillis(); + if (running && module instanceof TemporalModule) { + moduleId = "(none)"; + } + + if (running) hero.pet.clickable.setRadius(0); + else hero.pet.clickable.reset(); } - private void sleepMax(long time, int total) { - time = System.currentTimeMillis() - time; + public void setBehaviours(List behaviours) { + this.behaviours = behaviours; + } - if (time < total) { - try { - Thread.sleep(total - time); - } catch (InterruptedException ignored) { - } + private void checkModule() { + if (module == null || !Objects.equals(moduleId, config.GENERAL.CURRENT_MODULE)) { + Module module = featureRegistry.getFeature(moduleId = config.GENERAL.CURRENT_MODULE, Module.class) + .orElseGet(() -> { + String name = moduleId.substring(moduleId.lastIndexOf(".") + 1); + Popups.showMessageAsync("Error", I18n.get("bot.issue.module_load_failed", name), JOptionPane.ERROR_MESSAGE); + return new DummyModule(); + }); + setModule(module, true); } } + + public void setConfig(String config) { + if (configManager.getConfigName().equals(config)) return; + this.config = configManager.loadConfig(config); + mapManager.updateAreas(); + featureRegistry.updateConfig(); + form.updateConfiguration(); + setModule(module, true); + } + } diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/BackpageManager.java b/src/main/java/com/github/manolo8/darkbot/backpage/BackpageManager.java new file mode 100644 index 00000000..d12607dd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/BackpageManager.java @@ -0,0 +1,200 @@ +package com.github.manolo8.darkbot.backpage; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Task; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.utils.Base64Utils; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.Time; +import com.github.manolo8.darkbot.utils.http.Http; +import com.github.manolo8.darkbot.utils.http.Method; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BackpageManager extends Thread { + public static final Pattern RELOAD_TOKEN_PATTERN = Pattern.compile("reloadToken=([^\"]+)"); + private static final String[] ACTIONS = new String[]{ + "internalStart", "internalDock", "internalAuction", "internalGalaxyGates", "internalPilotSheet"}; + + public final HangarManager hangarManager; + public final LegacyHangarManager legacyHangarManager; + public final GalaxyManager galaxyManager; + + private final Main main; + private String sid, instance; + private List tasks; + + private long lastRequest; + private long sidLastUpdate = System.currentTimeMillis(); + private long sidNextUpdate = sidLastUpdate; + private long checkDrones = Long.MAX_VALUE; + private int sidStatus = -1; + + public BackpageManager(Main main) { + super("BackpageManager"); + this.main = main; + this.hangarManager = new HangarManager(main, this); + this.legacyHangarManager = new LegacyHangarManager(main, this); + this.galaxyManager = new GalaxyManager(main); + setDaemon(true); + start(); + } + + private static String getRandomAction() { + return ACTIONS[(int) (Math.random() * ACTIONS.length)]; + } + + @Override + @SuppressWarnings("InfiniteLoopStatement") + public void run() { + while (true) { + Time.sleep(100); + + if (isInvalid()) { + sidStatus = -1; + continue; + } + + this.hangarManager.tick(); + + if (System.currentTimeMillis() > sidNextUpdate) { + int waitTime = sidCheck(); + sidLastUpdate = System.currentTimeMillis(); + sidNextUpdate = sidLastUpdate + (int) (waitTime + waitTime * Math.random()); + galaxyManager.initIfEmpty(); + } + + if (System.currentTimeMillis() > checkDrones) { + try { + boolean checked = hangarManager.checkDrones(); + + System.out.println("Checked/repaired drones, all successful: " + checked); + + checkDrones = !checked ? System.currentTimeMillis() + 30_000 : Long.MAX_VALUE; + } catch (Exception e) { + System.err.println("Failed to check & repair drones, retry in 5m"); + checkDrones = System.currentTimeMillis() + 300_000; + e.printStackTrace(); + } + } + + for (Task task : tasks) { + synchronized (main.pluginHandler.getBackgroundLock()) { + try { + task.tickTask(); + } catch (Throwable e) { + main.featureRegistry.getFeatureDefinition(task) + .getIssues() + .addWarning(I18n.get("bot.issue.feature.failed_to_tick"), IssueHandler.createDescription(e)); + } + } + } + } + } + + public void checkDronesAfterKill() { + this.checkDrones = System.currentTimeMillis(); + } + + private boolean isInvalid() { + this.sid = main.statsManager.sid; + this.instance = main.statsManager.instance; + return sid == null || instance == null || sid.isEmpty() || instance.isEmpty(); + } + + private int sidCheck() { + try { + sidStatus = sidKeepAlive(); + } catch (Exception e) { + sidStatus = -2; + e.printStackTrace(); + return 5 * Time.MINUTE; + } + return 10 * Time.MINUTE; + } + + private int sidKeepAlive() throws Exception { + return getConnection("indexInternal.es?action=" + getRandomAction(), 5000).getResponseCode(); + } + + public HttpURLConnection getConnection(String params, int minWait) throws Exception { + Time.sleep(lastRequest + minWait - System.currentTimeMillis()); + return getConnection(params); + } + + public HttpURLConnection getConnection(String params) throws Exception { + if (isInvalid()) throw new UnsupportedOperationException("Can't connect when sid is invalid"); + HttpURLConnection conn = (HttpURLConnection) new URL(this.instance + params) + .openConnection(); + conn.setInstanceFollowRedirects(false); + conn.setRequestProperty("Cookie", "dosid=" + this.sid); + lastRequest = System.currentTimeMillis(); + return conn; + } + + public Http getConnection(String params, Method method, int minWait) { + Time.sleep(lastRequest + minWait - System.currentTimeMillis()); + return getConnection(params, method); + } + + public Http getConnection(String params, Method method) { + if (isInvalid()) throw new UnsupportedOperationException("Can't connect when sid is invalid"); + return Http.create(this.instance + params, method) + .setRawHeader("Cookie", "dosid=" + this.sid) + .addSupplier(() -> lastRequest = System.currentTimeMillis()); + } + + public String getDataInventory(String params) { + try { + return getConnection(params, Method.GET, 2500) + .setRawHeader("Content-Type", "application/x-www-form-urlencoded") + .consumeInputStream(Base64Utils::decode); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public String getReloadToken(InputStream input) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(input))) { + return br.lines() + .map(RELOAD_TOKEN_PATTERN::matcher) + .filter(Matcher::find) + .map(m -> m.group(1)) + .findFirst().orElse(null); + + } catch (IOException e){ + e.printStackTrace(); + return null; + } + } + + public void setTasks(List tasks) { + this.tasks = tasks; + } + + public synchronized String sidStatus() { + return sidStat() + (sidStatus != -1 && sidStatus != 302 ? + " " + Time.toString(System.currentTimeMillis() - sidLastUpdate) + "/" + + Time.toString(sidNextUpdate - sidLastUpdate) : ""); + } + + private String sidStat() { + switch (sidStatus) { + case -1: return "--"; + case -2: return "ERR"; + case 200: return "OK"; + case 302: return "KO"; + default: return sidStatus + ""; + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/FlashResManager.java b/src/main/java/com/github/manolo8/darkbot/backpage/FlashResManager.java new file mode 100644 index 00000000..0d4d5455 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/FlashResManager.java @@ -0,0 +1,63 @@ +package com.github.manolo8.darkbot.backpage; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Task; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.utils.XmlHelper; +import com.github.manolo8.darkbot.utils.http.Http; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilderFactory; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Responsible for searching & holding flash resource file, containing translations for in-game things + * + * To be able to undo translations (convert a translated text into it's original translation id) in an efficient + * manner we will substring all resources to the first N characters to be able to do an initial fast search. + */ +@Feature(name = "Flash resource manager", description = "Holds the resources for the in-game language") +public class FlashResManager implements Task { + + private static final String URL = "https://darkorbit-22.bpsecure.com/spacemap/templates/{lang}/flashres.xml"; + + private Main main; + + private String lang = null; + + private Map ALL_TRANSLATIONS = Collections.emptyMap(); + + + @Override + public void install(Main main) { + this.main = main; + } + + @Override + public void tick() { + if (main == null) return; + String currLang = main.settingsManager.lang; + if (currLang == null || currLang.isEmpty() || currLang.equals("ERROR") || currLang.equals(lang)) return; + + try { + Element root = Http.create(URL.replace("{lang}", currLang)) + .consumeInputStream(inputStream -> DocumentBuilderFactory + .newInstance() + .newDocumentBuilder() + .parse(inputStream) + .getDocumentElement()); + + ALL_TRANSLATIONS = XmlHelper.stream(root.getElementsByTagName("item")).collect(Collectors.toMap( + i -> i.getAttributes().getNamedItem("name").getNodeValue(), Node::getTextContent, (a,b) -> a)); + + // TODO: store in an efficient way to reverse-translate + lang = currLang; + } catch (Exception e) { + e.printStackTrace(); + lang = null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/GalaxyManager.java b/src/main/java/com/github/manolo8/darkbot/backpage/GalaxyManager.java new file mode 100644 index 00000000..0f4dc3fb --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/GalaxyManager.java @@ -0,0 +1,74 @@ +package com.github.manolo8.darkbot.backpage; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.backpage.entities.galaxy.GalaxyGate; +import com.github.manolo8.darkbot.backpage.entities.galaxy.GalaxyInfo; +import com.github.manolo8.darkbot.utils.http.Method; + +import javax.xml.parsers.DocumentBuilderFactory; + +public class GalaxyManager { + + private Main main; + private GalaxyInfo galaxyInfo; + private long lastGatesUpdate; + + GalaxyManager(Main main) { + this.main = main; + this.galaxyInfo = new GalaxyInfo(); + } + + public GalaxyInfo getGalaxyInfo() { + return galaxyInfo; + } + + public void prepareGate(GalaxyGate gate, int minWait) { + handleRequest(getLink("setupGate", true) + gate.getIdParam(), -1, minWait); + } + + public void performGateSpin(GalaxyGate gate, int useMultiAt, int spinAmount, int minWait) { + boolean useMultiplier = galaxyInfo.getGate(gate).getMultiplier() >= useMultiAt; + performGateSpin(gate, useMultiplier, spinAmount, minWait); + } + + public void performGateSpin(GalaxyGate gate, boolean multiplier, int spinAmount, int minWait) { + String params = getLink("multiEnergy", false) + gate.getParam(); + + if (galaxyInfo.getSamples() != null && galaxyInfo.getSamples() > 0) params += "&sample=1"; + if (multiplier) params += "&multiplier=1"; + if (spinAmount > 4) params += "&spinamount=" + spinAmount; + + handleRequest(params, -1, minWait); + } + + public void updateGalaxyInfo(int expiryTime) { + handleRequest(getLink("init", false), expiryTime, 1000); + } + + void initIfEmpty() { + if (galaxyInfo.getGates().isEmpty()) updateGalaxyInfo(2000); + } + + private String getLink(String action, boolean isInverted) { + return "flashinput/galaxyGates.php?userID=" + main.hero.id + + (isInverted ? "&sid=" + main.statsManager.sid + "&action=" + action + : "&action=" + action + "&sid=" + main.statsManager.sid); + } + + private void handleRequest(String params, int expiryTime, int minWait) { + if (System.currentTimeMillis() > lastGatesUpdate + expiryTime) { + try { + galaxyInfo.update(main.backpage.getConnection(params, Method.GET, minWait) + .addSupplier(() -> lastGatesUpdate = System.currentTimeMillis()) + .consumeInputStream(inputStream -> DocumentBuilderFactory + .newInstance() + .newDocumentBuilder() + .parse(inputStream) + .getDocumentElement())); + + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/HangarManager.java b/src/main/java/com/github/manolo8/darkbot/backpage/HangarManager.java new file mode 100644 index 00000000..833422e0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/HangarManager.java @@ -0,0 +1,202 @@ +package com.github.manolo8.darkbot.backpage; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.backpage.entities.Drone; +import com.github.manolo8.darkbot.backpage.entities.Item; +import com.github.manolo8.darkbot.backpage.entities.ItemInfo; +import com.github.manolo8.darkbot.backpage.entities.ShipInfo; +import com.github.manolo8.darkbot.backpage.hangar.Hangar; +import com.github.manolo8.darkbot.backpage.hangar.HangarResponse; +import com.github.manolo8.darkbot.core.itf.Tickable; +import com.github.manolo8.darkbot.utils.Base64Utils; +import com.github.manolo8.darkbot.utils.Time; +import com.github.manolo8.darkbot.utils.http.Method; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class HangarManager implements Tickable { + private final Gson gson = new Gson(); + + @Deprecated + private final LegacyHangarManager legacyHangarManager; + + private final Main main; + private final BackpageManager backpage; + + private HangarResponse hangarList; + private HangarResponse currentHangar; + + private volatile long updateHangarListEvery = -1, updateCurrentHangarEvery = -1; + private long lastHangarListUpdate, lastCurrentHangarUpdate; + + + public HangarManager(Main main, BackpageManager backpage) { + this.main = main; + this.backpage = backpage; + + this.legacyHangarManager = new LegacyHangarManager(main, backpage); + } + + @Override + public void tick() { + try { + if (updateHangarListEvery != -1 && + (lastHangarListUpdate + updateCurrentHangarEvery) < System.currentTimeMillis()) { + long timer = System.currentTimeMillis(); + updateHangarList(); + lastHangarListUpdate = System.currentTimeMillis(); + updateHangarListEvery = -1; + + System.out.println("HangarList updated in: " + (System.currentTimeMillis() - timer) + "ms"); + Time.sleep(500); + } + + if (updateCurrentHangarEvery != -1 && + (lastCurrentHangarUpdate + updateCurrentHangarEvery) < System.currentTimeMillis()) { + long timer = System.currentTimeMillis(); + updateCurrentHangar(); + lastCurrentHangarUpdate = System.currentTimeMillis(); + updateCurrentHangarEvery = -1; + + System.out.println("CurrentHangar updated in: " + (System.currentTimeMillis() - timer) + "ms"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public HangarResponse getHangarList() { + return hangarList; + } + + public HangarResponse getCurrentHangar() { + return currentHangar; + } + + /** + * Request hangar list to be updated within a certain timeframe. + * This method should be repeatedly called to request updates + * @param millis The maximum time to wait + */ + public void requestUpdateCurrentHangar(long millis) { + if (millis < 0) return; + this.updateCurrentHangarEvery = millis; + } + + /** + * Request current hangar to be updated within a certain timeframe. + * This method should be repeatedly called to request updates + * @param millis The maximum time to wait + */ + public void requestUpdateHangarList(long millis) { + if (millis < 0) return; + this.updateHangarListEvery = millis; + } + + public void updateCurrentHangar() throws Exception { + if (hangarList == null) updateHangarList(); + + for (Hangar hangar : hangarList.getData().getRet().getHangars()) + if (hangar.isActive()) { + this.currentHangar = getHangarResponseById(hangar.getHangarId()); + break; + } + } + + public void updateHangarList() throws Exception { + this.hangarList = deserializeHangar(getInputStream("getHangarList", new JsonObject())); + } + + public HangarResponse getHangarResponseById(int hangarId) throws Exception { + JsonObject paramObj = new JsonObject(); + JsonObject hangarObj = new JsonObject(); + + hangarObj.addProperty("hi", hangarId); + paramObj.add("params", hangarObj); + + return deserializeHangar(getInputStream("getHangar", paramObj)); + } + + public InputStream getInputStream(String action, JsonObject json) throws IOException { + return backpage.getConnection("flashAPI/inventory.php", Method.POST) + .setRawParam("action", action) + .setParam("params", Base64Utils.encode(json.toString())) + .getInputStream(); + } + + private HangarResponse deserializeHangar(InputStream in) throws Exception { + HangarResponse hangar = gson.fromJson(Base64Utils.decode(in), HangarResponse.class); + in.close(); + + if (hangar.getData().map != null) { + String[] lootIds = hangar.getData().map.get("lootIds"); + + hangar.getData().getRet().getItemInfos() + .forEach(itemInfo -> itemInfo.setLocalizationId(lootIds[itemInfo.getLootId()])); + + hangar.getData().map = null; + } + + return hangar; + } + + /* For backwards compatibility, keep methods from legacy hangar manager */ + @Deprecated + public boolean changeHangar(String hangarId) { + return legacyHangarManager.changeHangar(hangarId); + } + + @Deprecated + public Boolean checkDrones() { + return legacyHangarManager.checkDrones(); + } + + @Deprecated + public void updateHangars() { + legacyHangarManager.updateHangars(); + } + + @Deprecated + public String getActiveHangar() { + return legacyHangarManager.getActiveHangar(); + } + + @Deprecated + public void updateHangarData(int expiryTime) { + legacyHangarManager.updateHangarData(expiryTime); + } + + @Deprecated + public List getDrones() { + return legacyHangarManager.getDrones(); + } + + @Deprecated + public List getItems() { + return legacyHangarManager.getItems(); + } + + @Deprecated + public List getItemInfos() { + return legacyHangarManager.getItemInfos(); + } + + @Deprecated + public List getShipInfos() { + return legacyHangarManager.getShipInfos(); + } + + @Deprecated + public List getTypes() { + return legacyHangarManager.getTypes(); + } + + @Deprecated + public List getLootIds() { + return legacyHangarManager.getLootIds(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/LegacyHangarManager.java b/src/main/java/com/github/manolo8/darkbot/backpage/LegacyHangarManager.java new file mode 100644 index 00000000..0ab028d5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/LegacyHangarManager.java @@ -0,0 +1,186 @@ +package com.github.manolo8.darkbot.backpage; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.backpage.entities.Drone; +import com.github.manolo8.darkbot.backpage.entities.Hangar; +import com.github.manolo8.darkbot.backpage.entities.Item; +import com.github.manolo8.darkbot.backpage.entities.ItemInfo; +import com.github.manolo8.darkbot.backpage.entities.ShipInfo; +import com.github.manolo8.darkbot.utils.Base64Utils; +import com.github.manolo8.darkbot.utils.http.Method; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@Deprecated +public class LegacyHangarManager { + + private static final Gson GSON = new Gson(); + private static final JsonParser JSON_PARSER = new JsonParser(); + private static final Type DRONE_LIST = new TypeToken>(){}.getType(); + private static final Type ITEMINFO_LIST = new TypeToken>(){}.getType(); + private static final Type ITEM_LIST = new TypeToken>(){}.getType(); + private static final Type SHIPINFO_LIST = new TypeToken>(){}.getType(); + private static final Type STRING_LIST = new TypeToken>(){}.getType(); + + private final Main main; + private final BackpageManager backpageManager; + private long lastHangarChange = 0; + private List hangars; + private List drones; + private List items; + private List itemInfos; + private List shipInfos; + private List types; + private List lootIds; + private long lastHangarDataUpdate = 0; + + LegacyHangarManager(Main main, BackpageManager backpageManager) { + this.main = main; + this.backpageManager = backpageManager; + this.hangars = new ArrayList<>(); + this.drones = new ArrayList<>(); + this.items = new ArrayList<>(); + this.itemInfos = new ArrayList<>(); + this.shipInfos = new ArrayList<>(); + } + + public boolean changeHangar(String hangarId) { + if (lastHangarChange > System.currentTimeMillis() || !backpageManager.sidStatus().contains("OK")) return false; + + try { + String token = backpageManager.getConnection("indexInternal.es", Method.GET) + .setRawParam("action", "internalDock") + .consumeInputStream(backpageManager::getReloadToken); + + if (token == null || token.isEmpty()) return false; + + return !backpageManager.getConnection("indexInternal.es", Method.GET, 2000) + .addSupplier(() -> this.lastHangarChange = System.currentTimeMillis() + 12_000) + .setRawParam("action", "internalDock") + .setRawParam("subAction", "changeHangar") + .setRawParam("hangarId", hangarId) + .setRawParam("reloadToken", token) + .getContent().contains("\"type\":\"error\""); + + } catch (IOException e) { + e.printStackTrace(); + this.lastHangarChange = System.currentTimeMillis() + 5_000; + return false; + } + } + + public Boolean checkDrones() { + updateHangarData(500); + boolean repaired = !drones.isEmpty(); + for (Drone drone : drones) { + if (drone.getDamage() / 100d >= main.config.MISCELLANEOUS.DRONE_REPAIR_PERCENTAGE) { + repaired &= repairDrone(drone); + } + } + return repaired; + } + + private boolean repairDrone(Drone drone) { + try { + String encodeParams = Base64Utils.encode( "{\"action\":\"repairDrone\",\"lootId\":\"" + + drone.getLoot() + "\",\"repairPrice\":" + drone.getRepairPrice() + + ",\"params\":{\"hi\":" + getActiveHangar() + "}," + + "\"itemId\":\"" + drone.getItemId() + "\",\"repairCurrency\":\"" + drone.getRepairCurrency() + + "\",\"quantity\":1,\"droneLevel\":" + drone.getDroneLevel() + "}"); + String url = "flashAPI/inventory.php?action=repairDrone¶ms="+encodeParams; + String json = this.backpageManager.getDataInventory(url); + return json.contains("'isError':0"); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public void updateHangars() { + String params = "flashAPI/inventory.php?action=getHangarList"; + String json = backpageManager.getDataInventory(params); + + if (json == null || !(json.contains("'isError':0") || json.contains("\"isError\":0"))) return; + + JsonObject ret = JSON_PARSER.parse(json).getAsJsonObject().get("data").getAsJsonObject().get("ret").getAsJsonObject(); + hangars.clear(); + forEachHangar(ret, h -> hangars.add(GSON.fromJson(h, Hangar.class))); + } + + public String getActiveHangar() { + return hangars.stream().filter(Hangar::hangarIsActive).findFirst().map(Hangar::getHangarId).orElse(null); + } + + public void updateHangarData(int expiryTime) { + if (System.currentTimeMillis() > lastHangarDataUpdate + expiryTime) updateHangarData(); + } + + private void updateHangarData() { + updateHangars(); + String hangarId = getActiveHangar(); + if (hangarId == null) return; + + String encodeParams = Base64Utils.encode("{\"params\":{\"hi\":" + hangarId + "}}"); + String json = this.backpageManager.getDataInventory("flashAPI/inventory.php?action=getHangar¶ms=" + encodeParams); + + if (json != null) { + JsonObject data = JSON_PARSER.parse(json).getAsJsonObject().getAsJsonObject("data"); + JsonObject ret = data.getAsJsonObject("ret"); + forEachHangar(ret, h -> { + if (!h.get("hangar_is_active").getAsBoolean()) return; + this.drones = GSON.fromJson(h.getAsJsonObject("general").get("drones"), DRONE_LIST); + }); + this.items = GSON.fromJson(ret.get("items"), ITEM_LIST); + this.itemInfos = GSON.fromJson(ret.get("itemInfo"), ITEMINFO_LIST); + this.shipInfos = GSON.fromJson(ret.get("shipInfo"), SHIPINFO_LIST); + this.types = GSON.fromJson(data.getAsJsonObject("map").get("types"), STRING_LIST); + this.lootIds = GSON.fromJson(data.getAsJsonObject("map").get("lootIds"), STRING_LIST); + lastHangarDataUpdate = System.currentTimeMillis(); + } + } + + private void forEachHangar(JsonObject ret, Consumer consumer) { + try { + JsonElement val = ret.get("hangars"); + if (val instanceof JsonArray) val.getAsJsonArray().forEach(i -> consumer.accept(i.getAsJsonObject())); + else val.getAsJsonObject().entrySet().forEach(i -> consumer.accept(i.getValue().getAsJsonObject())); + } catch (Exception e) { + System.err.println("Failed to iterate hangars: " + ret); + throw e; + } + } + + public List getDrones() { + return drones; + } + + public List getItems() { + return items; + } + + public List getItemInfos() { + return itemInfos; + } + + public List getShipInfos() { + return shipInfos; + } + + public List getTypes() { + return types; + } + + public List getLootIds() { + return lootIds; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/UsernameUpdater.java b/src/main/java/com/github/manolo8/darkbot/backpage/UsernameUpdater.java new file mode 100644 index 00000000..635d3ab4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/UsernameUpdater.java @@ -0,0 +1,123 @@ +package com.github.manolo8.darkbot.backpage; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.PlayerInfo; +import com.github.manolo8.darkbot.config.UnresolvedPlayer; +import com.github.manolo8.darkbot.core.itf.Task; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.utils.Base62; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.IOUtils; +import com.github.manolo8.darkbot.utils.http.Method; + +import javax.swing.*; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Feature(name = "Username updater", description = "Updates player names & ids") +public class UsernameUpdater implements Task { + + private static final Pattern PROFILE_ID = Pattern.compile("/p/([A-Za-z0-9]+)-"); + + private Main main; + private Config config; + private BackpageManager backpageManager; + + + @Override + public void install(Main main) { + this.main = main; + this.config = main.config; + this.backpageManager = main.backpage; + } + + @Override + public void tick() { + this.config = main.config; + + UnresolvedPlayer user = config.UNRESOLVED.poll(); + if (user == null || (user.userId == -1 && user.username == null)) return; + if (user.shouldWait()) { + reQueue(user); + return; + } + + try { + boolean byId = user.userId != -1; + + UserResponse response = backpageManager.getConnection("ajax/" + (byId ? "user" : "pilotprofil") + ".php", Method.POST) + .setRawParam("command", "loadUserInfo") + .setRawParam("userId", Base62.encode(user.userId)) + .setRawParam("command", "searchProfileFromExternalPPP") + .setParam("profileUsername", user.username) + .consumeInputStream(inputStream -> Main.GSON.fromJson(IOUtils.read(inputStream), UserResponse.class)); + + if (response == null || !Objects.equals(response.result, "OK")) { + reQueue(user); + return; + } + + int id = response.getId(); + if (!byId && (Objects.equals(response.url, "false") || id == -1)) { + Popups.showMessageAsync(I18n.get("gui.players.not_found.title"), + I18n.get("gui.players.not_found.by_name", user.username), JOptionPane.WARNING_MESSAGE); + return; // Don't re-queue + } + + if (byId && (response.userName == null || response.userName.isEmpty())) { + if (user.username != null) return; // Probably just updating the player, ignore + Popups.showMessageAsync(I18n.get("gui.players.not_found.title"), + I18n.get("gui.players.not_found.by_id", user.userId), JOptionPane.WARNING_MESSAGE); + return; // Don't re-queue + } + + if (byId) user.username = response.getUsername(); + else user.userId = id; + } catch (Exception e) { + e.printStackTrace(); + } + + if (user.userId != -1 && user.username != null) { + PlayerInfo pl = config.PLAYER_INFOS.get(user.userId); + if (pl != null) { + pl.username = user.username; + pl.lastUpdate = System.currentTimeMillis(); + } else { + config.PLAYER_INFOS.put(user.userId, new PlayerInfo(user.username, user.userId)); + } + + config.PLAYER_UPDATED.send(user.userId); + config.changed = true; + } else reQueue(user); + } + + private void reQueue(UnresolvedPlayer user) { + user.retries++; + user.lastUpdate = System.currentTimeMillis(); + config.UNRESOLVED.add(user); + } + + private static class UserResponse { + String result; + String userName; + String url; + + private String getUsername() throws UnsupportedEncodingException { + return URLDecoder.decode(userName, "UTF-8"); + } + + private int getId() { + if (url == null) return -1; + Matcher m = PROFILE_ID.matcher(url); + if (!m.find()) return -1; + int id = Base62.decode(m.group(1)); + return id == 0 ? -1 : id; + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/Drone.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/Drone.java new file mode 100644 index 00000000..b26d52e3 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/Drone.java @@ -0,0 +1,73 @@ +package com.github.manolo8.darkbot.backpage.entities; + +import com.google.gson.annotations.SerializedName; + +public class Drone { + + @SerializedName("L") + private int lootId; + + @SerializedName("repair") + private int repairPrice; + + @SerializedName("I") + private String itemId; + @SerializedName("currency") + private String repairCurrency; + @SerializedName("LV") + private int droneLevel; + @SerializedName("HP") + private String damage; + @SerializedName("SL") + private int upgradeLevel; + + public Drone(int lootId, int repairPrice, String itemId, String repairCurrency, int droneLevel, String damage, int upgradeLevel) { + this.lootId = lootId; + this.repairPrice = repairPrice; + this.itemId = itemId; + this.repairCurrency = repairCurrency; + this.droneLevel = droneLevel; + this.damage = damage; + this.upgradeLevel = upgradeLevel; + } + + public String getLoot() { + switch (lootId){ + case 1: + return "drone_flax"; + case 2: + return "drone_iris"; + case 3: + return "drone_apis"; + case 4: + return "drone_zeus"; + } + + return "drone_iris"; + } + + public int getRepairPrice() { + return repairPrice; + } + + public String getItemId() { + return itemId; + } + + public String getRepairCurrency() { + return repairCurrency; + } + + public int getDroneLevel() { + return droneLevel; + } + + public int getDamage() { + return Integer.parseInt(damage.replaceAll("\\D+", "")); + } + + public int getUpgradeLevel(){ + return upgradeLevel; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/Hangar.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/Hangar.java new file mode 100644 index 00000000..0f0c392b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/Hangar.java @@ -0,0 +1,26 @@ +package com.github.manolo8.darkbot.backpage.entities; + +import com.google.gson.annotations.SerializedName; + +public class Hangar { + + @SerializedName("hangarID") + private String hangarId; + + @SerializedName("hangar_is_active") + private boolean hangarIsActive; + + public Hangar(String hangarId, boolean hangarIsActive) { + this.hangarId = hangarId; + this.hangarIsActive = hangarIsActive; + } + + public String getHangarId() { + return hangarId; + } + + public boolean hangarIsActive() { + return hangarIsActive; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/Item.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/Item.java new file mode 100644 index 00000000..a886517e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/Item.java @@ -0,0 +1,88 @@ +package com.github.manolo8.darkbot.backpage.entities; + +import com.google.gson.annotations.SerializedName; + +public class Item { + @SerializedName("L") + private int lootId; + + @SerializedName("EQC") + private String equipedConfi; + + @SerializedName("I") + private String itemId; + + @SerializedName("EQH") + private String equipedHangar; + + @SerializedName("EQT") + private String equipedTarget; + + @SerializedName("LV") + private int level; + + @SerializedName("CH") + private String loads; + + @SerializedName("Q") + private int quantity; + + @SerializedName("SL") + private int shieldUpgradeLevel; + + @SerializedName("DL") + private int damageUpgradeLevel; + + public Item(int lootId, String equipedConfi, String itemId, String equipedHangar, String equipedTarget, int level, String loads, int quantity, int shieldUpgradeLevel, int damageUpgradeLevel) { + this.lootId = lootId; + this.equipedConfi = equipedConfi; + this.itemId = itemId; + this.equipedHangar = equipedHangar; + this.equipedTarget = equipedTarget; + this.level = level; + this.loads = loads; + this.quantity = quantity; + this.shieldUpgradeLevel = shieldUpgradeLevel; + this.damageUpgradeLevel = damageUpgradeLevel; + } + + public int getLoot() { + return this.lootId; + } + + public String getEquipedConfi() { + return this.equipedConfi; + } + + public String getItemId() { + return this.itemId; + } + + public String getEquipedHangar() { + return this.equipedHangar; + } + + public String getEquipedTarget() { + return this.equipedTarget; + } + + public int getLevel() { + return this.level; + } + + public String getLoads() { + return this.loads; + } + + public int getQuantity() { + return this.quantity; + } + + public int getShieldUpgradeLevel() { + return this.shieldUpgradeLevel; + } + + public int getDamageUpgradeLevel() { + return this.damageUpgradeLevel; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/ItemInfo.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/ItemInfo.java new file mode 100644 index 00000000..311bb2e1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/ItemInfo.java @@ -0,0 +1,26 @@ +package com.github.manolo8.darkbot.backpage.entities; + +import com.google.gson.annotations.SerializedName; + +public class ItemInfo { + + @SerializedName("L") + private int lootId; + + @SerializedName("name") + private String name; + + public ItemInfo(int lootId, String name) { + this.lootId = lootId; + this.name = name; + } + + public int getLoot() { + return this.lootId; + } + + public String getName() { + return this.name; + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/ShipInfo.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/ShipInfo.java new file mode 100644 index 00000000..53668350 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/ShipInfo.java @@ -0,0 +1,66 @@ +package com.github.manolo8.darkbot.backpage.entities; + +import com.google.gson.annotations.SerializedName; + +public class ShipInfo { + + @SerializedName("lootId") + private String lootId; + + @SerializedName("owned") + private Integer owned; + + @SerializedName("fav") + private Integer fav; + + @SerializedName("hangarId") + private String hangarId; + + @SerializedName("factionBased") + private Integer factionBased; + + @SerializedName("eventItem") + private Integer eventItem; + + @SerializedName("eventActive") + private Integer eventActive; + + public ShipInfo(String lootId, Integer owned, Integer fav, String hangarId, Integer factionBased, Integer eventItem, Integer eventActive) { + this.lootId = lootId; + this.owned = owned; + this.fav = fav; + this.hangarId = hangarId; + this.factionBased = factionBased; + this.eventItem = eventItem; + this.eventActive = eventActive; + } + + public String getLootId() { + return lootId; + } + + public Integer getOwned() { + return owned; + } + + public Integer getFav() { + return fav; + } + + public String getHangarId() { + return hangarId; + } + + public Integer getFactionBased() { + return factionBased; + } + + public Integer getEventItem() { + return eventItem; + } + + public Integer getEventActive() { + return eventActive; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/GalaxyGate.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/GalaxyGate.java new file mode 100644 index 00000000..279230d9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/GalaxyGate.java @@ -0,0 +1,65 @@ +package com.github.manolo8.darkbot.backpage.entities.galaxy; + +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.manager.StarManager; +import com.github.manolo8.darkbot.core.objects.Map; + +import java.util.Set; +import java.util.stream.Collectors; + +public enum GalaxyGate { + ALPHA ("alpha", 1, "α"), + BETA ("beta", 2, "β"), + GAMMA ("gamma", 3, "γ"), + DELTA ("delta", 4, "δ"), + EPSILON("epsilon", 5, "ε"), + ZETA ("zeta", 6, "ζ"), + KAPPA ("kappa", 7, "κ"), + LAMBDA ("lambda", 8, "λ"), + // Just to get infos about Kronos + KRONOS ("kronos", 12, "Kronos"), + HADES ("hades", 13, "Hades"), + KUIPER ("streuner", 19, "ς"); + + private String name; + private int id; + private Set maps; + + GalaxyGate(String name, int id, String mapSymbol) { + this.name = name; + this.id = id; + this.maps = StarManager.getAllMaps().stream() + .filter(map -> map.gg && map.name.contains(mapSymbol)) + .collect(Collectors.toSet()); + } + + public String getParam() { + return "&gateID=" + getId() + "&" + getName() + "=1"; + } + + public String getIdParam() { + return "&gateID=" + getId(); + } + + public String getName() { + return name; + } + + public int getId() { + return id; + } + + public Set getMaps() { + return maps; + } + + public boolean isInGate(HeroManager hero) { + return getMaps().stream().anyMatch(map -> map.id == hero.map.id); + } + + boolean match(Object o) { + if (o instanceof Integer) return o.equals(getId()); + if (o instanceof String) return o.equals(getName()); + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/GalaxyInfo.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/GalaxyInfo.java new file mode 100644 index 00000000..56b95c81 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/GalaxyInfo.java @@ -0,0 +1,139 @@ +package com.github.manolo8.darkbot.backpage.entities.galaxy; + +import com.github.manolo8.darkbot.utils.XmlHelper; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class GalaxyInfo { + private Integer money; + private Integer samples; + private Integer selectedSpinAmount; + private Integer spinOnSale; + private Integer spinSalePercentage; + private Integer galaxyGateDay; + private Integer bonusRewardsDay; + private Integer energyCost; + + private final List items = new ArrayList<>(); + private final Map gates = new HashMap<>(); + + public void update(Element e) { + this.items.clear(); + + this.money = XmlHelper.valueToInt(e, "money"); + this.samples = XmlHelper.valueToInt(e, "samples"); + this.selectedSpinAmount = XmlHelper.valueToInt(e, "spinamount_selected"); + this.spinOnSale = XmlHelper.valueToInt(e, "spinOnSale"); + this.spinSalePercentage = XmlHelper.valueToInt(e, "spinSalePercentage"); + this.galaxyGateDay = XmlHelper.valueToInt(e, "galaxyGateDay"); + this.bonusRewardsDay = XmlHelper.valueToInt(e, "bonusRewardsDay"); + this.energyCost = XmlHelper.valueToInt(XmlHelper.getElement(e, "energy_cost")); + + if (XmlHelper.hasAnyElement(e, "gates")) updateGates(XmlHelper.getElement(e, "gates")); + if (XmlHelper.hasAnyElement(e, "items")) updateItems(XmlHelper.getElement(e, "items")); + if (XmlHelper.hasAnyElement(e, "multipliers")) updateMultipliers(XmlHelper.getElement(e, "multipliers")); + + if (XmlHelper.hasAnyElement(e, "setup")) { + Integer id = XmlHelper.attrToInt(XmlHelper.getElement(e, "setup"), "gate_id"); + Optional.ofNullable(getGate(id)).ifPresent(Gate::onGatePrepare); + } + } + + public Integer getMoney() { + return money; + } + + public Integer getSamples() { + return samples; + } + + public Integer getSelectedSpinAmount() { + return selectedSpinAmount; + } + + public Integer getSpinSalePercentage() { + return spinSalePercentage; + } + + public Integer getEnergyCost() { + return energyCost; + } + + public boolean isSpinOnSale() { + return spinOnSale != null && spinOnSale == 1; + } + + public boolean isGalaxyGateDay() { + return galaxyGateDay != null && galaxyGateDay == 1; + } + + public boolean isBonusRewardsDay() { + return bonusRewardsDay != null && bonusRewardsDay == 1; + } + + public List getItems() { + return items; + } + + public Gate getGate(Integer id) { + if (id == null) return null; + for (GalaxyGate gate : GalaxyGate.values()) + if (gate.getId() == id) return getGate(gate); + + return null; + } + + public Gate getGate(GalaxyGate gate) { + return getGates().get(gate); + } + + public Map getGates() { + return gates; + } + + private void updateItems(Element e) { + XmlHelper.stream(e.getElementsByTagName("item")) + .forEach(item -> { + Optional.ofNullable(getGate(XmlHelper.attrToInt(item, "gate_id"))).ifPresent(gate -> gate.update(item)); + this.items.add(new Item().update(item)); + }); + } + + private void updateGates(Element e) { + XmlHelper.stream(e.getElementsByTagName("gate")) + .forEach(gate -> Arrays.stream(GalaxyGate.values()) + .filter(g -> g.match(XmlHelper.attrToInt(gate, "id"))) + .findFirst() + .ifPresent(g -> this.gates.computeIfAbsent(g, k -> new Gate()).update(gate))); + } + + private void updateMultipliers(Element e) { + XmlHelper.stream(e.getElementsByTagName("multiplier")) + .forEach(multiplier -> Arrays.stream(GalaxyGate.values()) + .filter(g -> g.match(multiplier.getAttribute("mode"))) + .findFirst() + .ifPresent(g -> this.gates.computeIfAbsent(g, k -> new Gate()).setMultiplier(multiplier))); + } + + @Override + public String toString() { + return "GalaxyInfo{" + + "money=" + money + + ", samples=" + samples + + ", selectedSpinAmount=" + selectedSpinAmount + + ", spinOnSale=" + spinOnSale + + ", spinSalePercentage=" + spinSalePercentage + + ", galaxyGateDay=" + galaxyGateDay + + ", bonusRewardsDay=" + bonusRewardsDay + + ", energyCost=" + energyCost + + ", items=" + items + + ", gates=" + gates + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/Gate.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/Gate.java new file mode 100644 index 00000000..62f86257 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/Gate.java @@ -0,0 +1,144 @@ +package com.github.manolo8.darkbot.backpage.entities.galaxy; + +import com.github.manolo8.darkbot.utils.XmlHelper; +import org.w3c.dom.Element; + +public class Gate { + private String state; + + private Integer total; + private Integer current; + private Integer id; + private Integer prepared; + private Integer totalWave; + private Integer currentWave; + private Integer livesLeft; + private Integer lifePrice; + private Integer multiplier = -1; + + void update(Element e) { + setState (e.getAttribute("state")); + setTotal (XmlHelper.attrToInt(e, "total")); + setCurrent (XmlHelper.attrToInt(e, "current")); + setId (XmlHelper.attrToInt(e, "id")); + setId (XmlHelper.attrToInt(e, "gate_id")); + setPrepared (XmlHelper.attrToInt(e, "prepared")); + setTotalWave (XmlHelper.attrToInt(e, "totalWave")); + setCurrentWave(XmlHelper.attrToInt(e, "currentWave")); + setLivesLeft (XmlHelper.attrToInt(e, "livesLeft")); + setLifePrice (XmlHelper.attrToInt(e, "lifePrice")); + } + + public boolean shouldStopSpinning() { + return getLivesLeft() > 0 && isFinished(); + } + + public boolean isFinished() { + return getCurrent().equals(getTotal()); + } + + public String getState() { + return state; + } + + public Integer getTotal() { + return total; + } + + public Integer getCurrent() { + return current; + } + + public Integer getId() { + return id; + } + + public Integer getPrepared() { + return prepared; + } + + public Integer getTotalWave() { + return totalWave; + } + + public Integer getCurrentWave() { + return currentWave; + } + + public Integer getLivesLeft() { + return livesLeft; + } + + public Integer getLifePrice() { + return lifePrice; + } + + /** + * @return -1 if was never assigned. Only for KRONOS actually + */ + public Integer getMultiplier() { + return multiplier; + } + + void onGatePrepare() { + this.state = "on_progress"; + this.current = 0; + this.prepared = 1; + } + + void setMultiplier(Element multiplier) { + this.multiplier = XmlHelper.attrToInt(multiplier, "value"); + } + + private void setState(String state) { + if (state != null) this.state = state; + } + + private void setTotal(Integer total) { + if (total != null) this.total = total; + } + + private void setCurrent(Integer current) { + if (current != null) this.current = current; + } + + private void setId(Integer id) { + if (id != null) this.id = id; + } + + private void setPrepared(Integer prepared) { + if (prepared != null) this.prepared = prepared; + } + + private void setTotalWave(Integer totalWave) { + if (totalWave != null) this.totalWave = totalWave; + } + + private void setCurrentWave(Integer currentWave) { + if (currentWave != null) this.currentWave = currentWave; + } + + private void setLivesLeft(Integer livesLeft) { + if (livesLeft != null) this.livesLeft = livesLeft; + } + + private void setLifePrice(Integer lifePrice) { + if (lifePrice != null) this.lifePrice = lifePrice; + } + + @Override + public String toString() { + return "Gate{" + + "state='" + state + '\'' + + ", total=" + total + + ", current=" + current + + ", id=" + id + + ", prepared=" + prepared + + ", totalWave=" + totalWave + + ", currentWave=" + currentWave + + ", livesLeft=" + livesLeft + + ", lifePrice=" + lifePrice + + ", multiplier=" + multiplier + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/Item.java b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/Item.java new file mode 100644 index 00000000..58882df8 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/entities/galaxy/Item.java @@ -0,0 +1,99 @@ +package com.github.manolo8.darkbot.backpage.entities.galaxy; + +import com.github.manolo8.darkbot.utils.XmlHelper; +import org.w3c.dom.Element; + +/** + * Make sure to handle nulls + */ +public class Item { + private String date; + private String state; + private String type; + + private Integer gateId; + private Integer duplicate; + private Integer partId; + private Integer itemId; + private Integer amount; + private Integer current; + private Integer total; + private Integer multiplierUsed; + + Item update(Element e) { + this.date = e.getAttribute("date"); + this.state = e.getAttribute("state"); + this.type = e.getAttribute("type"); + this.gateId = XmlHelper.attrToInt(e, "gate_id"); + this.duplicate = XmlHelper.attrToInt(e, "duplicate"); + this.partId = XmlHelper.attrToInt(e, "part_id"); + this.itemId = XmlHelper.attrToInt(e, "item_id"); + this.amount = XmlHelper.attrToInt(e, "amount"); + this.current = XmlHelper.attrToInt(e, "current"); + this.total = XmlHelper.attrToInt(e, "total"); + this.multiplierUsed = XmlHelper.attrToInt(e, "multiplier_used"); + + return this; + } + + public String getDate() { + return date; + } + + public String getState() { + return state; + } + + public String getType() { + return type; + } + + public Integer getGateId() { + return gateId; + } + + public Integer getDuplicate() { + return duplicate; + } + + public Integer getPartId() { + return partId; + } + + public Integer getItemId() { + return itemId; + } + + public Integer getAmount() { + return amount; + } + + public Integer getCurrent() { + return current; + } + + public Integer getTotal() { + return total; + } + + public Integer getMultiplierUsed() { + return multiplierUsed; + } + + @Override + public String toString() { + return "Item{" + + "date='" + date + '\'' + + ", state='" + state + '\'' + + ", type='" + type + '\'' + + ", gateId=" + gateId + + ", duplicate=" + duplicate + + ", partId=" + partId + + ", itemId=" + itemId + + ", amount=" + amount + + ", current=" + current + + ", total=" + total + + ", multiplierUsed=" + multiplierUsed + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Configuration.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Configuration.java new file mode 100644 index 00000000..5183773b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Configuration.java @@ -0,0 +1,37 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import java.util.Map; + +public class Configuration { + private Equipment ship; + private Map drones; + private Equipment pet; + + public Equipment getShip() { + return ship; + } + + public Equipment getDrone(String id) { + return getDrones().get(id); + } + + /** + * Key is the ID of the drone. + */ + public Map getDrones() { + return drones; + } + + public Equipment getPet() { + return pet; + } + + @Override + public String toString() { + return "Config{" + + "ship=" + ship + + ", drones=" + drones + + ", pet=" + pet + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Data.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Data.java new file mode 100644 index 00000000..72cb5bc8 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Data.java @@ -0,0 +1,27 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class Data { + private Ret ret; + private Money money; + @Nullable public Map map; + + public Ret getRet() { + return ret; + } + + public Money getMoney() { + return money; + } + + @Override + public String toString() { + return "Data{" + + "ret=" + ret + + ", money=" + money + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Drone.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Drone.java new file mode 100644 index 00000000..9c7047e2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Drone.java @@ -0,0 +1,68 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public class Drone extends Item { + @SerializedName("HP") private String health; + @SerializedName("EF") private String droneEffect; + @SerializedName("BE") private Map droneBonusEffects; + @SerializedName("DE") private String droneDesignEffects; + @SerializedName("CU") private String droneDesign; + @SerializedName("DL") private String damageLevel; + @SerializedName("SL") private String shieldLevel; + @SerializedName("repair") private int repairCost; + @SerializedName("currency") private String currency; + + public String getHealth() { + return health; + } + + public String getDroneEffect() { + return droneEffect; + } + + public Map getDroneBonusEffects() { + return droneBonusEffects; + } + + public String getDroneDesignEffects() { + return droneDesignEffects; + } + + public String getDroneDesign() { + return droneDesign; + } + + public String getDamageLevel() { + return damageLevel; + } + + public String getShieldLevel() { + return shieldLevel; + } + + public int getRepairCost() { + return repairCost; + } + + public String getCurrency() { + return currency; + } + + @Override + public String toString() { + return "Drone{" + + "health='" + health + '\'' + + ", droneEffect='" + droneEffect + '\'' + + ", droneBonusEffects=" + droneBonusEffects + + ", droneDesignEffects='" + droneDesignEffects + '\'' + + ", droneDesign='" + droneDesign + '\'' + + ", damageLevel='" + damageLevel + '\'' + + ", shieldLevel='" + shieldLevel + '\'' + + ", repairCost=" + repairCost + + ", currency='" + currency + '\'' + + "} " + super.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Equipment.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Equipment.java new file mode 100644 index 00000000..13adc7d6 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Equipment.java @@ -0,0 +1,25 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public class Equipment { + @SerializedName("EQ") + private Map equipped; + + public int[] getEquippedOfType(String type) { + return getEquipped().get(type); + } + + public Map getEquipped() { + return equipped; + } + + @Override + public String toString() { + return "Equipment{" + + "equipped=" + equipped + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/EquippableItem.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/EquippableItem.java new file mode 100644 index 00000000..4f90114c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/EquippableItem.java @@ -0,0 +1,26 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public class EquippableItem extends Item { + private Map properties; + @SerializedName("Q") private Integer quantity; + + public Map getProperties() { + return properties; + } + + public Integer getQuantity() { + return quantity; + } + + @Override + public String toString() { + return "EquippableItem{" + + "properties=" + properties + + ", quantity=" + quantity + + "} " + super.toString(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/General.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/General.java new file mode 100644 index 00000000..446e7e8b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/General.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import java.util.List; + +public class General { + private Ship ship; + private Pet pet; + private List drones; + + public Ship getShip() { + return ship; + } + + public Pet getPet() { + return pet; + } + + public List getDrones() { + return drones; + } + + @Override + public String toString() { + return "General{" + + "ship=" + ship + + ", pet=" + pet + + ", drones=" + drones + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Hangar.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Hangar.java new file mode 100644 index 00000000..86a8235d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Hangar.java @@ -0,0 +1,59 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class Hangar { + @SerializedName("hangarID") private int hangarId; + @SerializedName("hangar_is_active") private boolean active; + @SerializedName("config") private Map configurations; + private General general; + + public int getHangarId() { + return hangarId; + } + + public boolean isActive() { + return active; + } + + public Configuration getConfiguration(int config) { + return getConfigurations().get(String.valueOf(config)); + } + + public Map getConfigurations() { + return configurations; + } + + public General getGeneral() { + return general; + } + + @Override + public String toString() { + return "Hangar{" + + "hangarId=" + hangarId + + ", active=" + active + + ", configurations=" + configurations + + ", general=" + general + + '}'; + } + + public static class HangarAdapter implements JsonDeserializer> { + + public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return json.isJsonArray() ? context.deserialize(json, typeOfT) : + json.getAsJsonObject().entrySet().stream() + .map(e -> (Hangar) context.deserialize(e.getValue(), Hangar.class)) + .collect(Collectors.toList()); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/HangarResponse.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/HangarResponse.java new file mode 100644 index 00000000..09e10313 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/HangarResponse.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +public class HangarResponse { + private int isError; + private Data data; + + public int getIsError() { + return isError; + } + + public Data getData() { + return data; + } + + @Override + public String toString() { + return "HangarResponse{" + + "isError=" + isError + + ", data=" + data + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Item.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Item.java new file mode 100644 index 00000000..362da2cd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Item.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +public abstract class Item { + @SerializedName("L") protected int lootId; + @SerializedName("LV") protected Integer level; + @SerializedName("I") protected String itemId; + + public int getLootId() { + return lootId; + } + + public Integer getLevel() { + return level; + } + + public String getItemId() { + return itemId; + } + + @Override + public String toString() { + return "Item{" + + "lootId=" + lootId + + ", level=" + level + + ", itemId='" + itemId + '\'' + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/ItemInfo.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/ItemInfo.java new file mode 100644 index 00000000..4b0999c1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/ItemInfo.java @@ -0,0 +1,71 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Map; + +public class ItemInfo extends Item { + private String name; + private String localizationId; + @SerializedName("C") private String category; + private List> levels; + + public String getName() { + return name; + } + + public String getLocalizationId() { + return localizationId; + } + + public void setLocalizationId(String localizationId) { + this.localizationId = localizationId; + } + + public String getCategory() { + return category; + } + + public List> getLevels() { + return levels; + } + + public BufferedImage getBufferedImage(IconType type) { + try { + return ImageIO.read(new URL("http://www.darkorbit.com/do_img/global/items/" + + getLocalizationId().replace("_", "/") + "_" + type.urlName + ".png")); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public enum IconType { + SMALL("30x30"),MEDIUM("100x100"),TOP("top"); + String urlName; + + IconType(String name) { + this.urlName = name; + } + + @Override + public String toString() { + return this.urlName; + } + } + + @Override + public String toString() { + return "ItemInfo{" + + "name='" + name + '\'' + + ", localizationId='" + localizationId + '\'' + + ", category='" + category + '\'' + + ", levels=" + levels + + "} " + super.toString(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Money.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Money.java new file mode 100644 index 00000000..2dfba8d2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Money.java @@ -0,0 +1,21 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +public class Money { + private String uridium, credits; + + public String getUridium() { + return uridium; + } + + public String getCredits() { + return credits; + } + + @Override + public String toString() { + return "Money{" + + "uridium='" + uridium + '\'' + + ", credits='" + credits + '\'' + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Pet.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Pet.java new file mode 100644 index 00000000..f5a398e2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Pet.java @@ -0,0 +1,32 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +public class Pet extends Item { + @SerializedName("HP") private String health; + @SerializedName("PN") private String name; + private Map lockedSlots; + + public String getHealth() { + return health; + } + + public String getName() { + return name; + } + + public Map getLockedSlots() { + return lockedSlots; + } + + @Override + public String toString() { + return "Pet{" + + "health='" + health + '\'' + + ", name='" + name + '\'' + + ", lockedSlots=" + lockedSlots + + "} " + super.toString(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Ret.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Ret.java new file mode 100644 index 00000000..d64fdc6a --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Ret.java @@ -0,0 +1,67 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; + +import java.util.List; +import java.util.Map; + +public class Ret { + private Map filters; + @JsonAdapter(value = Hangar.HangarAdapter.class) private List hangars; + @SerializedName("items") private List items; + @SerializedName("itemInfo") private List itemInfos; + @SerializedName("shipInfo") private List shipInfos; + + public int[] getTypeFilters(FilterType type) { + return getFilters().get(type.toString()); + } + + public Map getFilters() { + return filters; + } + + public List getHangars() { + return hangars; + } + + public List getItems() { + return items; + } + + public List getItemInfos() { + return itemInfos; + } + + public List getShipInfos() { + return shipInfos; + } + + @Override + public String toString() { + return "Ret{" + + "filters=" + filters + + ", hangars=" + hangars + + ", items=" + items + + ", itemInfos=" + itemInfos + + ", shipInfos=" + shipInfos + + '}'; + } + + public enum FilterType { + AMMUNITION, + DRONE_RELATED, + EXTRAS, + GENERATORS, + MODULES, + PET_RELATED, + RESOURCES, + SHIP_UPGRADES, + WEAPONS; + + @Override + public String toString() { + return name().toLowerCase(); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Ship.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Ship.java new file mode 100644 index 00000000..4c328c25 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/Ship.java @@ -0,0 +1,46 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +import java.util.Arrays; + +public class Ship { + @SerializedName("I") private int shipId; + @SerializedName("L") private int lootId; + @SerializedName("HP") private String health; + @SerializedName("SM") private String selectedDesign; + @SerializedName("M") private String[] availableDesigns; + /*@SerializedName("SCE") // dunno for now + @SerializedName("SCU")*/ + + public int getShipId() { + return shipId; + } + + public int getLootId() { + return lootId; + } + + public String getHealth() { + return health; + } + + public String getSelectedDesign() { + return selectedDesign; + } + + public String[] getAvailableDesigns() { + return availableDesigns; + } + + @Override + public String toString() { + return "Ship{" + + "shipId=" + shipId + + ", lootId=" + lootId + + ", health='" + health + '\'' + + ", selectedDesign='" + selectedDesign + '\'' + + ", availableDesigns=" + Arrays.toString(availableDesigns) + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/backpage/hangar/ShipInfo.java b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/ShipInfo.java new file mode 100644 index 00000000..9b96c16d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/backpage/hangar/ShipInfo.java @@ -0,0 +1,85 @@ +package com.github.manolo8.darkbot.backpage.hangar; + +import com.google.gson.annotations.SerializedName; + +public class ShipInfo { + private String lootId; + private int owned; + @SerializedName("fav") private int favourite; + private Price price; + private int hangarId; + private int factionBased; + private int eventItem; + private int eventActive; + + public String getLootId() { + return lootId; + } + + public int getOwned() { + return owned; + } + + public int getFavourite() { + return favourite; + } + + public Price getPrice() { + return price; + } + + public int getHangarId() { + return hangarId; + } + + public int getFactionBased() { + return factionBased; + } + + public int getEventItem() { + return eventItem; + } + + public int getEventActive() { + return eventActive; + } + + @Override + public String toString() { + return "ShipInfo{" + + "lootId='" + lootId + '\'' + + ", owned=" + owned + + ", favourite=" + favourite + + ", price=" + price + + ", hangarId=" + hangarId + + ", factionBased=" + factionBased + + ", eventItem=" + eventItem + + ", eventActive=" + eventActive + + '}'; + } + + public static class Price { + private int uridium, credits, isPayment; + + public int getUridium() { + return uridium; + } + + public int getCredits() { + return credits; + } + + public int getIsPayment() { + return isPayment; + } + + @Override + public String toString() { + return "Price{" + + "uridium=" + uridium + + ", credits=" + credits + + ", isPayment=" + isPayment + + '}'; + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/ActionInfo.java b/src/main/java/com/github/manolo8/darkbot/config/ActionInfo.java new file mode 100644 index 00000000..e7bdf16a --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/ActionInfo.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.config; + +import com.github.manolo8.darkbot.config.types.Option; + +@Option("Action") +public class ActionInfo { + @Option("Active") + public boolean active; + @Option("Check every") + public int checkEvery; + @Option("Condition") + public String condition; + @Option("Cooldown") + public int cooldown; + @Option("Action") + public String action; + @Option("Limit") + public int limit; +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/BoxInfo.java b/src/main/java/com/github/manolo8/darkbot/config/BoxInfo.java index c6e50477..261d9dee 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/BoxInfo.java +++ b/src/main/java/com/github/manolo8/darkbot/config/BoxInfo.java @@ -1,8 +1,13 @@ package com.github.manolo8.darkbot.config; -public class BoxInfo { +import com.github.manolo8.darkbot.config.types.Option; - public int waitTime; +@Option(key = "config.collect.box_table.resource") +public class BoxInfo { + @Option(key = "config.collect.box_table.collect") public boolean collect; - + @Option(key = "config.collect.box_table.wait") + public int waitTime; + @Option(key = "config.collect.box_table.priority") + public int priority; } diff --git a/src/main/java/com/github/manolo8/darkbot/config/Config.java b/src/main/java/com/github/manolo8/darkbot/config/Config.java index 90a3f419..0a7b1c4d 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/Config.java +++ b/src/main/java/com/github/manolo8/darkbot/config/Config.java @@ -1,51 +1,239 @@ package com.github.manolo8.darkbot.config; +import com.github.manolo8.darkbot.config.types.Editor; +import com.github.manolo8.darkbot.config.types.Num; +import com.github.manolo8.darkbot.config.types.Option; +import com.github.manolo8.darkbot.config.types.Options; +import com.github.manolo8.darkbot.config.types.Tag; +import com.github.manolo8.darkbot.config.types.TagDefault; +import com.github.manolo8.darkbot.config.types.suppliers.ApiSupplier; +import com.github.manolo8.darkbot.config.types.suppliers.DisplayFlag; +import com.github.manolo8.darkbot.config.types.suppliers.LanguageSupplier; +import com.github.manolo8.darkbot.config.types.suppliers.ModuleSupplier; +import com.github.manolo8.darkbot.config.types.suppliers.PetGearSupplier; +import com.github.manolo8.darkbot.config.types.suppliers.ReviveSpotSupplier; +import com.github.manolo8.darkbot.core.manager.StarManager; import com.github.manolo8.darkbot.core.utils.Lazy; - +import com.github.manolo8.darkbot.gui.tree.components.JActionTable; +import com.github.manolo8.darkbot.gui.tree.components.JBoxInfoTable; +import com.github.manolo8.darkbot.gui.tree.components.JCheckboxListField; +import com.github.manolo8.darkbot.gui.tree.components.JListField; +import com.github.manolo8.darkbot.gui.tree.components.JNpcInfoTable; +import com.github.manolo8.darkbot.gui.tree.components.JPercentField; +import com.github.manolo8.darkbot.gui.tree.components.LangEditor; +import com.github.manolo8.darkbot.modules.LootNCollectorModule; + +import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; -public class Config { - - public int WORKING_MAP = 26; - - public int OFFENSIVE_CONFIG = 1; - public char OFFENSIVE_FORMATION = '8'; - - public int RUN_CONFIG = 2; - public char RUN_FORMATION = '9'; +import static com.github.manolo8.darkbot.config.types.suppliers.DisplayFlag.*; - public long REFRESH_TIME = 60 * 60 * 1000; - - public int CURRENT_MODULE; - - public int MAX_DEATHS = 10; - - public int REPAIR_LOCAL = 0; +public class Config { - public double REPAIR_HP; - public double WAIT_HP; + // Defined map areas + public Map AVOIDED = new HashMap<>(); + public Map PREFERRED = new HashMap<>(); + public Map> SAFETY = new HashMap<>(); + public transient Lazy ADDED_SAFETY = new Lazy.NoCache<>(); - //ENTITIES - public HashMap boxInfos = new HashMap<>(); - public HashMap npcInfos = new HashMap<>(); - //ENTITIES + // Player list & resolver + public Map PLAYER_INFOS = new HashMap<>(); + public Set PLAYER_TAGS = new HashSet<>(); + public transient Lazy PLAYER_UPDATED = new Lazy.NoCache<>(); + public Queue UNRESOLVED = new LinkedList<>(); - //LOOT MODULE - public boolean RUN_FROM_ENEMIES; - public boolean RUN_FROM_ENEMIES_IN_SIGHT; - public char AMMO_KEY = '3'; - public boolean AUTO_SAB = true; - public char AUTO_SAB_KEY = '4'; - //LOOT MODULE - //COLLECTOR MODULE - public boolean STAY_AWAY_FROM_ENEMIES; - public boolean AUTO_CLOACK; - public char AUTO_CLOACK_KEY; - //COLLECTOR MODULE + // Plugin custom configuration objects + public Map CUSTOM_CONFIGS = new HashMap<>(); + public Map PLUGIN_INFOS = new HashMap<>(); public transient boolean changed; - public transient Lazy addedBox = new Lazy<>(); - public transient Lazy addedNpc = new Lazy<>(); + public @Option General GENERAL = new General(); + public static class General { + @Options(ModuleSupplier.class) + public @Option @Editor(JListField.class) String CURRENT_MODULE = LootNCollectorModule.class.getCanonicalName(); + @Options(StarManager.MapList.class) + public @Option @Editor(JListField.class) int WORKING_MAP = 26; + public @Option ShipConfig OFFENSIVE = new ShipConfig(1, '8'); + public @Option ShipConfig ROAM = new ShipConfig(1, '9'); + public @Option ShipConfig RUN = new ShipConfig(2, '9'); + public @Option @Num(max = 3600) int FORMATION_CHECK = 180; + + public @Option Safety SAFETY = new Safety(); + public static class Safety { + public @Option PercentRange REPAIR_HP_RANGE = new PercentRange(0.4, 0.95); + public @Option @Editor(JPercentField.class) double REPAIR_HP_NO_NPC = 0.5; + public @Option @Editor(JPercentField.class) double REPAIR_TO_SHIELD = 1; + public @Option ShipConfig REPAIR = new ShipConfig(1, '9'); + public @Option @Num(min = 1, max = 9999) int MAX_DEATHS = 10; + public @Option @Editor(JListField.class) @Options(ReviveSpotSupplier.class) long REVIVE_LOCATION = 1L; + public @Option @Num(min = 2, max = 60, step = 10) int WAIT_BEFORE_REVIVE = 3; + public @Option @Num(min = 3, max = 15 * 60, step = 10) int WAIT_AFTER_REVIVE = 90; + } + + public @Option Running RUNNING = new Running(); + public static class Running { + public @Option boolean RUN_FROM_ENEMIES = true; + public @Option @Num(max = 24 * 60 * 60, step = 300) int REMEMBER_ENEMIES_FOR = 300; + public @Option boolean RUN_FROM_ENEMIES_SIGHT = false; + public @Option boolean STOP_RUNNING_NO_SIGHT = true; + public @Option @Num(min = 500, max = 20000, step = 500) int MAX_SIGHT_DISTANCE = 4000; + public @Option Character SHIP_ABILITY; + public @Option @Num(max = 20000, step = 500) int SHIP_ABILITY_MIN = 1500; + public @Option @Num(max = 20000, step = 500) int RUN_FURTHEST_PORT = 1500; + } + + public @Option Roaming ROAMING = new Roaming(); + public static class Roaming { + public @Option boolean KEEP = true; + public @Option boolean SEQUENTIAL = false; + public @Option boolean ONLY_KILL_PREFERRED = false; + } + } + + public @Option Collect COLLECT = new Collect(); + public static class Collect { + public @Option boolean STAY_AWAY_FROM_ENEMIES; + public @Option boolean AUTO_CLOACK; + public @Option Character AUTO_CLOACK_KEY; + public @Option @Num(max = 10000, step = 50) int RADIUS = 400; + + @Option + @Editor(value = JBoxInfoTable.class, shared = true) + public Map BOX_INFOS = new HashMap<>(); + public transient Lazy ADDED_BOX = new Lazy.NoCache<>(); + } + + public @Option Loot LOOT = new Loot(); + public static class Loot { + public @Option Sab SAB = new Sab(); + public @Option Rsb RSB = new Rsb(); + public static class Sab { + public @Option boolean ENABLED = false; + public @Option Character KEY = '2'; + public @Option @Editor(JPercentField.class) double PERCENT = 0.8; + public @Option @Num(min = 500, max = 1_000_000, step = 1000) int NPC_AMOUNT = 12000; + } + public static class Rsb { + public @Option boolean ENABLED = false; + public @Option Character KEY = '3'; + public @Option @Num(min = 500, max = 60_000, step = 500) int AMMO_REFRESH = 3500; + } + public @Option Character AMMO_KEY = '1'; + public @Option Character SHIP_ABILITY; + public @Option @Num(min = 50_000, max = 5_000_000, step = 50_000) int SHIP_ABILITY_MIN = 150_000; + public @Option @Num(max = 10, step = 1) int MAX_CIRCLE_ITERATIONS = 5; + public @Option boolean RUN_CONFIG_IN_CIRCLE = true; + + public @Option boolean GROUP_NPCS = true; + @Editor(value = JNpcInfoTable.class, shared = true) + public @Option Map NPC_INFOS = new HashMap<>(); + public transient Lazy MODIFIED_NPC = new Lazy.NoCache<>(); + + public @Option @Num(min = 1000, max = 20000, step = 500) int NPC_DISTANCE_IGNORE = 3000; + } + + public @Option PetSettings PET = new PetSettings(); + public static class PetSettings { + public @Option boolean ENABLED = false; + public @Deprecated int MODULE = 0; // Kept so plugins using it don't just break. They'll just be unable to use pet. + public @Option(value = "Legacy mode (old plugin compatibility)", description = "Enable if an older plugin is managing pet. The new pet selector becomes useless.") boolean COMPATIBILITY_MODE = false; + public @Option @Editor(JListField.class) @Options(PetGearSupplier.class) int MODULE_ID = 1; + } + + public @Option GroupSettings GROUP = new GroupSettings(); + public static class GroupSettings { + public @Option boolean ACCEPT_INVITES = false; + public @Option @Tag(TagDefault.ALL) PlayerTag WHITELIST_TAG = null; + public @Option @Tag(TagDefault.NONE) PlayerTag INVITE_TAG = null; + public @Option boolean OPEN_INVITES = false; + //public @Option @Tag(TagDefault.NONE) PlayerTag KICK_TAG = null; + } + + public @Option Miscellaneous MISCELLANEOUS = new Miscellaneous(); + public static class Miscellaneous { + public @Option @Num(max = 60 * 12, step = 10) int REFRESH_TIME = 60; + public @Option @Num(max = 60 * 12, step = 10) int PAUSE_FOR = 5; + public @Option boolean RESET_REFRESH = true; + public @Option @Editor(JPercentField.class) double DRONE_REPAIR_PERCENTAGE = 0.9; + public @Option boolean HONOR_LOST_EXACT = true; + public @Option boolean LOG_CHAT = false; + public @Option boolean LOG_DEATHS = false; + } + + public @Option BotSettings BOT_SETTINGS = new BotSettings(); + public static class BotSettings { + public @Option @Editor(LangEditor.class) @Options(LanguageSupplier.class) + Locale LOCALE = new Locale(Locale.getDefault().getLanguage()); + public @Option boolean FORCE_GAME_LANGUAGE = false; + public @Option boolean SPOOF_CLIENT = false; + public @Option @Num(min = 10, max = 300) int ZONE_RESOLUTION = 30; + public @Option boolean MAP_START_STOP = false; + public @Option boolean CONFIRM_EXIT = true; + public @Option @Num(min = 10, max = 250) int MIN_TICK = 15; + public @Option boolean DEV_STUFF = false; + public @Option @Editor(JListField.class) @Options(ApiSupplier.class) int API = 2; + public @Option boolean FULLY_HIDE_API = true; + public @Option boolean DISABLE_MASTER_PASSWORD = false; + + public @Option Display DISPLAY = new Display(); + public static class Display { + public @Option @Editor(JCheckboxListField.class) @Options(DisplayFlag.Supplier.class) + Set TOGGLE = EnumSet.of( + HERO_NAME, HP_SHIELD_NUM, ZONES, STATS_AREA, BOOSTER_AREA, GROUP_NAMES, GROUP_AREA); + public @Option @Num(max = 300, step = 1) int TRAIL_LENGTH = 15; + public @Option @Num(min = 1, max = 20, step = 1) int BUTTON_SIZE = 4; + + public int width = 1280; + public int height = 800; + public boolean ALWAYS_ON_TOP = true; // No @Option. Edited via button + } + } + + public /*@Option("Extra actions")*/ ExtraActions EXTRA = new ExtraActions(); + public static class ExtraActions { + @Option + @Editor(value = JActionTable.class, shared = true) + public Map ACTION_INFOS = new HashMap<>(); + public transient Lazy MODIFIED_ACTIONS = new Lazy.NoCache<>(); + } + + public static class ShipConfig { + public int CONFIG = 1; + public Character FORMATION; + + public ShipConfig() {} + public ShipConfig(int CONFIG, Character FORMATION) { + this.CONFIG = CONFIG; + this.FORMATION = FORMATION; + } + + @Override + public String toString() { + return "Config: " + CONFIG + " Formation: " + Objects.toString(FORMATION, "(unset)"); + } + } + + public static class PercentRange { + public double min, max; + + public PercentRange() {} + public PercentRange(double min, double max) { + this.min = min; + this.max = max; + } + + @Override + public String toString() { + return Math.round(min * 100) + "%-" + Math.round(max * 100) + "%"; + } + } } diff --git a/src/main/java/com/github/manolo8/darkbot/config/ConfigEntity.java b/src/main/java/com/github/manolo8/darkbot/config/ConfigEntity.java index 7c614491..6061fe9b 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/ConfigEntity.java +++ b/src/main/java/com/github/manolo8/darkbot/config/ConfigEntity.java @@ -1,65 +1,102 @@ package com.github.manolo8.darkbot.config; +import com.github.manolo8.darkbot.core.entities.Entity; import com.github.manolo8.darkbot.core.manager.MapManager; +import com.github.manolo8.darkbot.extensions.plugins.PluginDefinition; -public class ConfigEntity { - - public static ConfigEntity INSTANCE; +import java.util.HashSet; +import java.util.Set; - private final Config config; +public class ConfigEntity { - public ConfigEntity(Config config) { - this.config = config; - INSTANCE = this; - } + public static ConfigEntity INSTANCE = new ConfigEntity(); + private Config config; public NpcInfo getOrCreateNpcInfo(String name) { - int mapId = MapManager.id; - - NpcInfo info = config.npcInfos.get(name); - + NpcInfo info = config.LOOT.NPC_INFOS.get(name); if (info == null) { info = new NpcInfo(); - info.radius = 400; + info.radius = 500; info.mapList.add(mapId); if (!name.equals("ERROR") && !name.isEmpty()) { - config.npcInfos.put(name, info); - - config.addedNpc.send(name); + config.LOOT.NPC_INFOS.put(name, info); + config.LOOT.MODIFIED_NPC.send(name); config.changed = true; } + } else if (info.mapList.add(mapId)) { + config.changed = true; + config.LOOT.MODIFIED_NPC.send(name); + } + return info; + } - } else { + public BoxInfo getOrCreateBoxInfo(String name) { + BoxInfo info = config.COLLECT.BOX_INFOS.get(name); + if (info == null) { + info = new BoxInfo(); + if (!name.equals("ERROR") && !name.isEmpty()) { + config.COLLECT.BOX_INFOS.put(name, info); + config.COLLECT.ADDED_BOX.send(name); - if (info.mapList.add(mapId)) { config.changed = true; } - } - return info; } - public BoxInfo getOrCreateBoxInfo(String name) { + public void updateSafetyFor(Entity entity) { + if (!entity.locationInfo.isLoaded() || + (entity.locationInfo.now.x == 0 && entity.locationInfo.now.y == 0)) return; + SafetyInfo.Type type = SafetyInfo.Type.of(entity); + if (type == null) return; + + Set safetyInfos = getOrCreateSafeties(); + + config.ADDED_SAFETY.send(safetyInfos.stream().filter(info -> info.type == type + && info.x == (int) entity.locationInfo.now.x + && info.y == (int) entity.locationInfo.now.y) + .peek(info -> info.entity = entity) + .findFirst() + .orElseGet(() -> { + SafetyInfo s = new SafetyInfo(type, (int) entity.locationInfo.now.x, (int) entity.locationInfo.now.y, entity); + safetyInfos.add(s); + config.changed = true; + return s; + })); + } - BoxInfo info = config.boxInfos.get(name); + public ZoneInfo getOrCreatePreferred() { + if (MapManager.id < 0) return new ZoneInfo(1); + return config.PREFERRED.computeIfAbsent(MapManager.id, id -> new ZoneInfo(config.BOT_SETTINGS.ZONE_RESOLUTION)); + } - if (info == null) { - info = new BoxInfo(); + public ZoneInfo getOrCreateAvoided() { + if (MapManager.id < 0) return new ZoneInfo(1); + return config.AVOIDED.computeIfAbsent(MapManager.id, id -> new ZoneInfo(config.BOT_SETTINGS.ZONE_RESOLUTION)); + } - if (!name.equals("ERROR") && !name.isEmpty()) { - config.boxInfos.put(name, info); + public Set getOrCreateSafeties() { + return config.SAFETY.computeIfAbsent(MapManager.id, id -> new HashSet<>()); + } + + public PluginInfo getPluginInfo(PluginDefinition plugin) { + return config.PLUGIN_INFOS.computeIfAbsent(plugin.name + "_by_" + plugin.author, id -> new PluginInfo()); + } - config.addedBox.send(name); + public static void changed() { + INSTANCE.config.changed = true; + } - config.changed = true; - } - } + public Config getConfig() { + return config; + } - return info; + public void setConfig(Config config) { + this.config = config; } + } diff --git a/src/main/java/com/github/manolo8/darkbot/config/ConfigManager.java b/src/main/java/com/github/manolo8/darkbot/config/ConfigManager.java new file mode 100644 index 00000000..cc7a51b3 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/ConfigManager.java @@ -0,0 +1,166 @@ +package com.github.manolo8.darkbot.config; + +import com.github.manolo8.darkbot.config.utils.ByteArrayToBase64TypeAdapter; +import com.github.manolo8.darkbot.config.utils.SpecialTypeAdapter; +import com.github.manolo8.darkbot.core.api.DarkBoatAdapter; +import com.github.manolo8.darkbot.core.api.DarkBotApiAdapter; +import com.github.manolo8.darkbot.core.api.DarkFlashApiAdapter; +import com.github.manolo8.darkbot.core.IDarkBotAPI; +import com.github.manolo8.darkbot.core.api.NativeApiAdapter; +import com.github.manolo8.darkbot.core.api.NoopApiAdapter; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import javax.swing.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class ConfigManager { + // Used for debug mode + public static boolean FORCE_NO_OP = false; + + public static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() + .setLenient() + .registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()) + .registerTypeAdapterFactory(new SpecialTypeAdapter()) + .create(); + + public static final String DEFAULT = "config", + CONFIG_FOLDER = "configs", + BACKUP = "_old", + EXTENSION = ".json"; + + private String configName; + private Config config; + private boolean failedConfig; + + public String getConfigName() { + return this.configName == null ? DEFAULT : this.configName; + } + + public Config getConfig() { + return this.config; + } + + public Path getConfigFile() { + return isDefault() ? Paths.get(getConfigName() + EXTENSION) : + Paths.get(CONFIG_FOLDER, getConfigName() + EXTENSION); + } + + public Path getConfigBackupFile() { + return isDefault() ? Paths.get(getConfigName() + BACKUP + EXTENSION) : + Paths.get(CONFIG_FOLDER, getConfigName() + BACKUP + EXTENSION); + } + + public boolean isDefault() { + return configName == null; + } + + public boolean getConfigFailed() { + return failedConfig; + } + + public Config loadConfig(String configName) { + if (DEFAULT.equals(configName)) configName = null; + this.configName = configName; + this.config = loadConfig(getConfigFile(), getConfigBackupFile()); + return this.config; + } + + + private Config loadConfig(Path configFile, Path backupFile) { + boolean existsConfig = Files.exists(configFile), + existsBackup = Files.exists(backupFile); + + Config config = null; + + if (existsConfig) config = loadConfig(configFile); + + if (existsBackup && config == null) { + config = loadConfig(backupFile); + if (config != null) + saveConfig(config, configFile, backupFile); + } + + failedConfig = config == null && (existsConfig || existsBackup); + + if (config == null) { + config = new Config(); + // Didn't have any previous config, save this new one: + if (!failedConfig) saveConfig(config, configFile, backupFile); + } + ConfigEntity.INSTANCE.setConfig(config); + + return config; + } + + public IDarkBotAPI getAPI() { + if (FORCE_NO_OP) return new NoopApiAdapter(); + try { + if (config.BOT_SETTINGS.API == 0) return new DarkBotApiAdapter(); + else if (config.BOT_SETTINGS.API == 1) return new DarkFlashApiAdapter(); + else if (config.BOT_SETTINGS.API == 2) return new DarkBoatAdapter(); + else if (config.BOT_SETTINGS.API == 3) return new NativeApiAdapter(); + else if (config.BOT_SETTINGS.API == 4) return new NoopApiAdapter(); + else throw new IllegalArgumentException("API not found: " + config.BOT_SETTINGS.API); + } catch (Error e) { + System.out.println("Error enabling API #" + config.BOT_SETTINGS.API + ", using no-op api"); + e.printStackTrace(); + config.BOT_SETTINGS.API = 4; + Popups.showMessageAsync( + "API failed to load", + "The API you had selected is not able to load.\n" + + "You probably do not have the required DLL in your lib folder.\n" + + "The bot will start on no-operation API, change it in the settings and restart.", + JOptionPane.ERROR_MESSAGE); + return new NoopApiAdapter(); + } + } + + public void saveChangedConfig() { + if (!config.changed) return; + config.changed = false; + saveConfig(); + } + + private Config loadConfig(Path file) { + try (BufferedReader reader = Files.newBufferedReader(file)) { + return GSON.fromJson(reader, Config.class); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public void saveConfig() { + if (failedConfig) return; // Don't save defaults if config failed to load! + saveConfig(getConfig(), getConfigFile(), getConfigBackupFile()); + } + + private static void saveConfig(Config config, Path configFile, Path backupFile) { + if (Files.exists(configFile)) { + try { + Files.move(configFile, backupFile, + StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + System.err.println("Couldn't move config before updating save file"); + e.printStackTrace(); + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(configFile)) { + GSON.toJson(config, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/NpcExtra.java b/src/main/java/com/github/manolo8/darkbot/config/NpcExtra.java new file mode 100644 index 00000000..eb5f5e6e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/NpcExtra.java @@ -0,0 +1,35 @@ +package com.github.manolo8.darkbot.config; + +import com.github.manolo8.darkbot.core.itf.NpcExtraProvider; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.utils.I18n; + +import java.util.Locale; + +public enum NpcExtra implements NpcExtraFlag { + NO_CIRCLE, IGNORE_OWNERSHIP, IGNORE_ATTACKED, AGGRESSIVE_FOLLOW, PASSIVE, ATTACK_SECOND, USE_RSB, PET_LOCATOR; + + @Override + public String getShortName() { + return I18n.getOrDefault("config.loot.npc_table.extra." + name().toLowerCase(Locale.ROOT) + ".short", name()); + } + + @Override + public String getName() { + return I18n.getOrDefault("config.loot.npc_table.extra." + name().toLowerCase(Locale.ROOT), name()); + } + + @Override + public String getDescription() { + return I18n.getOrDefault("config.loot.npc_table.extra." + name().toLowerCase(Locale.ROOT) + ".desc", name()); + } + + @Feature(name = "Npc extra flag provider", description = "Provides default npc extra flags") + public static class DefaultNpcExtraProvider implements NpcExtraProvider { + @Override + public NpcExtraFlag[] values() { + return NpcExtra.values(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/config/NpcExtraFlag.java b/src/main/java/com/github/manolo8/darkbot/config/NpcExtraFlag.java new file mode 100644 index 00000000..d865483c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/NpcExtraFlag.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.config; + +public interface NpcExtraFlag { + String getName(); + String getShortName(); + String getDescription(); + default String getKey() { + return null; + } + + default String getId() { + return getClass().getCanonicalName() + (getClass().isEnum() ? ((Enum) this).name() : getName()); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/NpcInfo.java b/src/main/java/com/github/manolo8/darkbot/config/NpcInfo.java index 86f6381d..d068e32b 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/NpcInfo.java +++ b/src/main/java/com/github/manolo8/darkbot/config/NpcInfo.java @@ -1,15 +1,100 @@ package com.github.manolo8.darkbot.config; +import com.github.manolo8.darkbot.config.types.Option; +import com.github.manolo8.darkbot.core.itf.NpcExtraProvider; + +import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +@Option(key = "config.loot.npc_table.name") public class NpcInfo { + @Option(key = "config.loot.npc_table.radius") + public double radius; + @Option(key = "config.loot.npc_table.priority") public int priority; + @Option(key = "config.loot.npc_table.kill") + public boolean kill; + @Option(key = "config.loot.npc_table.attack_key") + public Character attackKey; + @Option(key = "config.loot.npc_table.attack_formation") + public Character attackFormation; - public HashSet mapList = new HashSet<>(); + public int npcId; - public boolean kill; - public boolean killOnlyIfIsLast; + public static transient final Map NPC_FLAGS = new LinkedHashMap<>(); - public double radius; + public static void setNpcFlags(Stream flags) { + NPC_FLAGS.clear(); + flags.map(NpcExtraProvider::values) + .flatMap(Arrays::stream) + .forEach(flag -> NPC_FLAGS.put(flag.getId(), flag)); + } + + @Option(key = "config.loot.npc_table.extra") + public ExtraNpcInfo extra = new ExtraNpcInfo(); + + public static class ExtraNpcInfo { + private Set flags = new HashSet<>(); + + public ExtraNpcInfo() {} + + public ExtraNpcInfo(NpcExtraFlag... flags) { + for (NpcExtraFlag flag : flags) set(flag, true); + } + + public boolean has(NpcExtraFlag flag) { + return has(flag.getId()); + } + + public boolean has(String flagId) { + return flags.contains(flagId); + } + + public void set(NpcExtraFlag flag, boolean set) { + set(flag.getId(), set); + } + + public void set(String flagId, boolean set) { + if (set) flags.add(flagId); + else flags.remove(flagId); + } + + public String toString() { + return flags.stream().map(NPC_FLAGS::get) + .filter(Objects::nonNull) + .map(NpcExtraFlag::getShortName) + .collect(Collectors.joining(",")); + } + } + + public Set mapList = new HashSet<>(); + + public void set(Double radius, Integer priority, Boolean kill, Character attackKey, ExtraNpcInfo extra) { + set(radius, priority, kill, attackKey, null, extra); + } + + public void set(Double radius, Integer priority, Boolean kill, Character attackKey, Character attackFormation, ExtraNpcInfo extra) { + if (radius != null) this.radius = radius; + if (priority != null) this.priority = priority; + if (kill != null) this.kill = kill; + if (attackKey != null) this.attackKey = attackKey; + if (attackFormation != null) this.attackFormation = attackFormation; + if (extra != null) this.extra = extra; + } + + public void copyOf(NpcInfo other) { + this.radius = other.radius; + this.priority = other.priority; + this.kill = other.kill; + this.attackKey = other.attackKey; + this.attackFormation = other.attackFormation; + this.extra.flags = new HashSet<>(other.extra.flags); + } } diff --git a/src/main/java/com/github/manolo8/darkbot/config/PlayerInfo.java b/src/main/java/com/github/manolo8/darkbot/config/PlayerInfo.java new file mode 100644 index 00000000..e47abb20 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/PlayerInfo.java @@ -0,0 +1,53 @@ +package com.github.manolo8.darkbot.config; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class PlayerInfo { + public String username; + public int userId; + + public long lastUpdate = System.currentTimeMillis(); + + public Map subscriptions = new HashMap<>(); + + public PlayerInfo() {} + + public PlayerInfo(String username, int userId) { + this.username = username; + this.userId = userId; + } + + public void setTag(PlayerTag tag, Long until) { + subscriptions.put(tag, until == null ? -1 : until); + } + + public void removeTag(PlayerTag tag) { + subscriptions.remove(tag); + } + + public boolean hasTag(PlayerTag tag) { + Long until = subscriptions.get(tag); + if (until != null && (until == -1 || until > System.currentTimeMillis())) return true; + subscriptions.remove(tag); + return false; + } + + public Collection getTags() { + return subscriptions.keySet(); + } + + public boolean filter(String string) { + return string == null + || username.toLowerCase(Locale.ROOT).contains(string) + || String.valueOf(userId).contains(string) + || getTags().stream().anyMatch(tag -> tag.name.toLowerCase(Locale.ROOT).contains(string)); + } + + @Override + public String toString() { + return username + "(" + userId +")"; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/PlayerTag.java b/src/main/java/com/github/manolo8/darkbot/config/PlayerTag.java new file mode 100644 index 00000000..20396011 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/PlayerTag.java @@ -0,0 +1,72 @@ +package com.github.manolo8.darkbot.config; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.awt.*; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@JsonAdapter(PlayerTag.TagAdapter.class) +public class PlayerTag { + private static transient final Map INSTANCES = new HashMap<>(); + + public static PlayerTag getTag(String name, Color color) { + return getTag(new PlayerTag(name, color).toString()); + } + + public static PlayerTag getTag(String strTag) { + return INSTANCES.computeIfAbsent(strTag, in -> { + String[] str = in.split(",", 2); + return new PlayerTag(str[1], Color.decode(str[0])); + }); + } + + public String name; + public Color color; + + private PlayerTag(String name, Color color) { + this.name = name; + this.color = color; + } + + public boolean has(PlayerInfo playerInfo) { + return playerInfo != null && playerInfo.hasTag(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlayerTag playerTag = (PlayerTag) o; + return Objects.equals(name, playerTag.name) && + Objects.equals(color, playerTag.color); + } + + @Override + public int hashCode() { + return Objects.hash(name, color); + } + + @Override + public String toString() { + return String.format("#%02x%02x%02x,%s", color.getRed(), color.getGreen(), color.getBlue(), name); + } + + public static class TagAdapter extends TypeAdapter { + @Override + public void write(JsonWriter writer, PlayerTag tag) throws IOException { + writer.value(tag.toString()); + } + + @Override + public PlayerTag read(JsonReader in) throws IOException { + return getTag(in.nextString()); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/PluginInfo.java b/src/main/java/com/github/manolo8/darkbot/config/PluginInfo.java new file mode 100644 index 00000000..b76e2f56 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/PluginInfo.java @@ -0,0 +1,11 @@ +package com.github.manolo8.darkbot.config; + +import java.util.HashSet; +import java.util.Set; + +public class PluginInfo { + + public Set DISABLED_FEATURES = new HashSet<>(); + public Set ENABLED_FEATURES = new HashSet<>(); + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/SafetyInfo.java b/src/main/java/com/github/manolo8/darkbot/config/SafetyInfo.java new file mode 100644 index 00000000..48e925f1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/SafetyInfo.java @@ -0,0 +1,133 @@ +package com.github.manolo8.darkbot.config; + +import com.github.manolo8.darkbot.config.utils.Ignorable; +import com.github.manolo8.darkbot.core.entities.BattleStation; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.entities.bases.BaseHeadquarters; +import com.github.manolo8.darkbot.core.entities.bases.BaseSpot; +import com.github.manolo8.darkbot.core.entities.bases.BaseStation; +import com.github.manolo8.darkbot.utils.I18n; + +import java.io.Serializable; +import java.util.Locale; +import java.util.Objects; + +public class SafetyInfo implements Serializable, Ignorable { + public enum Type { + PORTAL, CBS, BASE; + public static Type of(Entity entity) { + if (entity instanceof Portal) return PORTAL; + if (entity instanceof BattleStation && // FIXME: split BattleStation classes + ((BattleStation) entity).hullId >= 0 && ((BattleStation) entity).hullId < 255) return CBS; + if (entity instanceof BaseSpot) return BASE; + return null; + } + public String toString() { + return I18n.getOrDefault("safety_places.type." + name().toLowerCase(Locale.ROOT), name()); + } + } + + public Type type; + public int x, y; + public transient Entity entity; + public transient double distance; + + private transient final RunMode defaultRunMode; // Used to determine if it should be stored + public RunMode runMode = RunMode.ALWAYS; + public JumpMode jumpMode; + public CbsMode cbsMode; + + public SafetyInfo() { + this.defaultRunMode = null; + } + + public SafetyInfo(Type type, int x, int y, Entity entity) { + this.type = type; + this.x = x; + this.y = y; + this.entity = entity; + this.runMode = this.defaultRunMode = getDefaultRunMode(); + if (type == Type.PORTAL) jumpMode = JumpMode.ESCAPING; + if (type == Type.CBS) cbsMode = CbsMode.ALLY; + } + + private RunMode getDefaultRunMode() { + return (type == Type.PORTAL && ((Portal) entity).target != null && !((Portal) entity).target.gg) || + type == Type.BASE && (entity instanceof BaseHeadquarters || entity instanceof BaseStation) ? + RunMode.ALWAYS : RunMode.NEVER; + } + + @Override + public boolean ignore() { + return (x == 0 && y == 0) || + runMode == defaultRunMode && + jumpMode == (type == Type.PORTAL ? JumpMode.ESCAPING : null) && + cbsMode == (type == Type.CBS ? CbsMode.ALLY : null); + } + + @Override + public boolean writeAsNull() { + return false; + } + + // Running reasons this safety can be selected + public enum RunMode { + ALWAYS, ENEMY_FLEE_ONLY, REPAIR_ONLY, REFRESH, NEVER; + public String toString() { + return I18n.getOrDefault("safety_places.run_mode." + name().toLowerCase(Locale.ROOT), name()); + } + } + + // PORTAL + // Condition to jump + public enum JumpMode { + NEVER, ESCAPING, FLEEING, REPAIRING, ALWAYS, ALWAYS_OTHER_SIDE; + public String toString() { + return I18n.getOrDefault("safety_places.jump_mode." + name().toLowerCase(Locale.ROOT), name()); + } + } + + // CBS + // Condition to run to CBS + public enum CbsMode { + ALLY, ALLY_NEUTRAL; + public String toString() { + return I18n.getOrDefault("safety_places.cbs_mode." + name().toLowerCase(Locale.ROOT), name()); + } + } + + public int diameter() { + return type != Type.BASE ? 500 : + entity instanceof BaseHeadquarters ? 2500 : + entity instanceof BaseStation ? 3000 : 500; + } + + public int radius() { + return diameter() / 2; + } + + @Override + public String toString() { + String result = type.toString(); + if (type == Type.PORTAL && entity != null && ((Portal) entity).target != null) + result += "(" + ((Portal) entity).target.name + ")"; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SafetyInfo that = (SafetyInfo) o; + return x == that.x && + y == that.y && + type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(type, x, y); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/UnresolvedPlayer.java b/src/main/java/com/github/manolo8/darkbot/config/UnresolvedPlayer.java new file mode 100644 index 00000000..44758997 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/UnresolvedPlayer.java @@ -0,0 +1,27 @@ +package com.github.manolo8.darkbot.config; + +/** + * Queued player waiting for ID or Username to be resolved by a background runner + */ +public class UnresolvedPlayer { + public String username; + public int userId = -1; + + public int retries = 0; + public long lastUpdate = 0; + + public UnresolvedPlayer() {} + + public UnresolvedPlayer(String username) { + this.username = username; + } + + public UnresolvedPlayer(int userId) { + this.userId = userId; + } + + public boolean shouldWait() { + return System.currentTimeMillis() < lastUpdate + (retries * retries * 1000); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/ZoneInfo.java b/src/main/java/com/github/manolo8/darkbot/config/ZoneInfo.java new file mode 100644 index 00000000..3ec3525d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/ZoneInfo.java @@ -0,0 +1,186 @@ +package com.github.manolo8.darkbot.config; + +import com.github.manolo8.darkbot.config.utils.Ignorable; +import com.github.manolo8.darkbot.core.manager.MapManager; +import com.github.manolo8.darkbot.core.utils.Location; +import com.github.manolo8.darkbot.core.utils.pathfinder.FixedTwoOptHeuristicTSP; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.interfaces.HamiltonianCycleAlgorithm; +import org.jgrapht.alg.tour.HeldKarpTSP; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleWeightedGraph; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class ZoneInfo implements Serializable, Ignorable { + private static final byte[] MASKS = new byte[8]; + static { + for (int i = 0; i < MASKS.length; i++) MASKS[i] = (byte) (1 << i); + } + + public int resolution = 1; + private byte[] data; + private transient List zones; + public transient boolean changed = true; + private transient boolean ordered = false; + + public ZoneInfo() {} + + public ZoneInfo(int resolution) { + this.resolution = resolution; + data = new byte[((resolution * resolution) + 7) / 8]; + } + + @Override + public boolean ignore() { + if (!changed) return zones.isEmpty(); + for (int x = 0; x < resolution; x++) + for (int y = 0; y < resolution; y++) + if (get(x, y)) return false; + return true; + } + + @Override + public boolean writeAsNull() { + return true; + } + + public void setResolution(int resolution) { + if (resolution == this.resolution) return; + ZoneInfo newZone = new ZoneInfo(resolution); + + for (int x = 0; x < resolution; x++) + for (int y = 0; y < resolution; y++) + if (get(x, y)) newZone.set(x, y); + + this.resolution = resolution; + this.data = newZone.data; + } + + public boolean get(int x, int y) { + if (outside(x, y)) return false; + int pos = x + (y * resolution); + return (data[pos / 8] & MASKS[pos % 8]) != 0; + } + + private void set(int x, int y) { + if (outside(x, y)) return; + int pos = x + (y * resolution); + data[pos / 8] |= MASKS[pos % 8]; + changed = true; + } + + private void remove(int x, int y) { + if (outside(x, y)) return; + int pos = x + (y * resolution); + data[pos / 8] &= ~MASKS[pos % 8]; + changed = true; + } + + private void toggle(int x, int y) { + if (outside(x, y)) return; + int pos = x + (y * resolution); + data[pos / 8] ^= MASKS[pos % 8]; + changed = true; + } + + private void set(int x, int y, int x2, int y2) { + for (; x < x2; x++) for (int currY = y; currY < y2; currY++) set(x, currY); + } + + private void remove(int x, int y, int x2, int y2) { + for (; x < x2; x++) for (int currY = y; currY < y2; currY++) remove(x, currY); + } + + public void toggle(int x, int y, int x2, int y2) { + for (; x < x2; x++) for (int currY = y; currY < y2; currY++) toggle(x, currY); + } + + public void set(int x, int y, int x2, int y2, boolean state) { + if (state) set(x, y, x2, y2); + else remove(x, y, x2, y2); + } + + private boolean outside(int x, int y) { + return x < 0 || y < 0 || x >= resolution || y >= resolution; + } + + public boolean contains(double xPercent, double yPercent) { + return get(Math.min((int) (xPercent * resolution), resolution - 1), Math.min((int) (yPercent * resolution), resolution - 1)); + } + + public boolean contains(Location loc) { + return contains(loc.x / MapManager.internalWidth, loc.y / MapManager.internalHeight); + } + + public List getZones() { + if (changed) { + zones = new ArrayList<>(); + for (int x = 0; x < resolution; x++) { + for (int y = 0; y < resolution; y++) + if (get(x, y)) zones.add(new Zone(x, y)); + } + changed = false; + ordered = false; + } + return zones; + } + + public List getSortedZones() { + getZones(); // Update zones + if (!ordered) { + ordered = true; + if (zones.size() <= 1 || zones.size() > 1000) return zones; + + SimpleWeightedGraph zoneGraph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); + for (ZoneInfo.Zone zone : zones) zoneGraph.addVertex(zone); // Add vertex + for (int i = 0; i < zones.size(); i++) { + ZoneInfo.Zone from = zones.get(i); + for (int j = i + 1; j < zones.size(); j++) { // Add connections + ZoneInfo.Zone to = zones.get(j); + zoneGraph.addEdge(from, to); + zoneGraph.setEdgeWeight(from, to, Math.sqrt(Math.pow(from.x - to.x, 2) + Math.pow(from.y - to.y, 2))); + } + } + HamiltonianCycleAlgorithm alg; + if (zones.size() <= 20) { + // For small graphs, find the best path + alg = new HeldKarpTSP<>(); + } else { + // Make less passes the bigger the graph, keeping an ok time. + alg = new FixedTwoOptHeuristicTSP<>(Math.max(Math.min(1500 - (zones.size() * 10), 750), 1)); + } + GraphPath path = alg.getTour(zoneGraph); + zones = path.getVertexList(); + zones.remove(0); + } + return zones; + } + + public class Zone { + public int x, y; + public Zone(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Finds the inner point for a width and height scale + * @param xProp The 0-1 proportion in the X position inside the zone + * @param yProp The 0-1 proportion in the Y position inside the zone + * @param width The total width of the area this zone belongs to + * @param height The total height of the area this zone belongs to + * @return The location for the x & y proportion inside the zone, for the area this belongs to. + */ + public Location innerPoint(double xProp, double yProp, double width, double height) { + double cellSize = 1d / resolution; + double xProportion = (x / (double) resolution) + xProp * cellSize, + yProportion = (y / (double) resolution) + yProp * cellSize; + + return new Location(xProportion * width, yProportion * height); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigField.java b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigField.java new file mode 100644 index 00000000..d17da816 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigField.java @@ -0,0 +1,54 @@ +package com.github.manolo8.darkbot.config.tree; + + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.types.Editor; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import java.lang.reflect.Field; + +/** + * Represents a java field in an object. Can act as getter/setter. + */ +public class ConfigField { + public final Object parent; + public final Field field; + + public ConfigField(Object parent, Field field) { + this.parent = parent; + this.field = field; + } + + public boolean isPrimitive() { + return field.getType().isPrimitive(); + } + + public T get() { + try { + return (T) field.get(parent); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public void set(Object value) { + try { + field.set(parent, value); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + ConfigEntity.changed(); + } + + public Class getEditor() { + Editor editor = field.getAnnotation(Editor.class); + return editor == null ? null : editor.value(); + } + + public boolean isSharedEditor() { + Editor editor = field.getAnnotation(Editor.class); + return editor != null && editor.shared(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigNode.java b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigNode.java new file mode 100644 index 00000000..7084dda5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigNode.java @@ -0,0 +1,148 @@ +package com.github.manolo8.darkbot.config.tree; + +import com.github.manolo8.darkbot.config.types.Option; +import com.github.manolo8.darkbot.config.types.Options; +import com.github.manolo8.darkbot.config.types.suppliers.OptionList; +import com.github.manolo8.darkbot.gui.tree.components.JActionTable; +import com.github.manolo8.darkbot.gui.tree.components.JBoxInfoTable; +import com.github.manolo8.darkbot.gui.tree.components.JFileOpener; +import com.github.manolo8.darkbot.gui.tree.components.JListField; +import com.github.manolo8.darkbot.gui.tree.components.JNpcInfoTable; +import com.github.manolo8.darkbot.gui.tree.components.JPercentField; +import com.github.manolo8.darkbot.gui.utils.Strings; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.ReflectionUtils; +import com.github.manolo8.darkbot.utils.StringQuery; + +import java.lang.reflect.Field; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +public abstract class ConfigNode { + + protected final Parent parent; + public final String name; + public final String description; + public final String key; + + ConfigNode(Parent parent, String name, String description, String key, String field) { + this.parent = parent; + this.name = name; + this.description = !description.isEmpty() ? description : null; + this.key = !key.isEmpty() ? key + : parent == null ? field.toLowerCase(Locale.ROOT) + : parent.key == null ? null + : parent.key + "." + field.toLowerCase(Locale.ROOT); + } + + public int getDepth() { + ConfigNode node = this; + int levels = 0; + while((node = node.parent) != null) levels++; + return levels; + } + + public String getLongestSibling() { + return parent.longestChild; + } + + // Convenient for searching/filtering + public String convertToString() { + String n = I18n.getOrDefault(key, name); + String desc = I18n.getOrDefault(key + ".desc", description); + + return (key != null ? key.substring(key.lastIndexOf(".") + 1) + " , " : "") + + (n != null ? n + " , " : "") + + (desc != null ? desc : ""); + } + + boolean isVisible(StringQuery query) { + if (match(query)) return true; + ConfigNode p = this; + while ((p = p.parent) != null) if (p.match(query)) return true; + return false; + } + + protected boolean match(StringQuery query) { + return query.matches(convertToString()); + } + + static ConfigNode.Parent rootingFrom(Parent parent, String name, Object root, String baseKey) { + return new Parent(parent, name, "", baseKey, null).addChildren(p -> Arrays.stream(root.getClass().getFields()) + .filter(f -> f.getAnnotation(Option.class) != null) + .map(f -> ConfigNode.of(p, new ConfigField(root, f))).toArray(ConfigNode[]::new)); + } + + private static ConfigNode of(Parent parent, ConfigField field) { + Field[] children = Arrays.stream(field.field.getType().getDeclaredFields()) + .filter(f -> f.getAnnotation(Option.class) != null) + .toArray(Field[]::new); + + if (children.length == 0) return new Leaf(parent, field); + Object obj = field.get(); + Option op = field.field.getAnnotation(Option.class); + return new Parent(parent, op.value(), op.description(), op.key(), field.field.getName()) + .addChildren(p -> Arrays.stream(children) + .map(f -> ConfigNode.of(p, new ConfigField(obj, f))).toArray(ConfigNode[]::new)); + + } + + static class Parent extends ConfigNode { + ConfigNode[] children; + String longestChild; + + Parent(Parent parent, String name, String description, String key, String field) { + super(parent, name, description, key, field); + } + + Parent addChildren(Function children) { + this.children = children.apply(this); + longestChild = Arrays.stream(this.children) + .filter(c -> c instanceof Leaf) + .map(c -> I18n.getOrDefault(c.key, c.name)) + .filter(name -> !name.isEmpty()) + .max(Comparator.comparingInt(String::length)).orElse(null); + return this; + } + + boolean isVisible(StringQuery filter) { + return super.isVisible(filter) || Arrays.stream(children).anyMatch(n -> n.isVisible(filter)); + } + + } + + public static class Leaf extends ConfigNode { + public final ConfigField field; + + Leaf(Parent parent, ConfigField field, Option option) { + super(parent, option.value(), option.description(), option.key(), field.field.getName()); + this.field = field; + } + + Leaf(Parent parent, ConfigField field) { + this(parent, field, field.field.getAnnotation(Option.class)); + } + + @Override + public String toString() { + Object obj = field.get(); + if (field.getEditor() == JPercentField.class) return DecimalFormat.getPercentInstance().format(obj); + if (field.getEditor() == JListField.class) { + OptionList options = ReflectionUtils.createInstance(field.field.getAnnotation(Options.class).value()); + return options.getText(obj); + } + if (field.getEditor() == JBoxInfoTable.class) return "Box infos (" + ((Map) obj).size() + ")"; + if (field.getEditor() == JNpcInfoTable.class) return "Npc infos (" + ((Map) obj).size() + ")"; + if (field.getEditor() == JActionTable.class) return "Action infos (" + ((Map) obj).size() + ")"; + if (field.getEditor() == JFileOpener.class) return Strings.fileName((String) obj); + return Objects.toString(obj, "(unset)"); + } + + } +} + diff --git a/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigTree.java b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigTree.java new file mode 100644 index 00000000..73fe692c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/tree/ConfigTree.java @@ -0,0 +1,116 @@ +package com.github.manolo8.darkbot.config.tree; + +import com.github.manolo8.darkbot.utils.StringQuery; + +import javax.swing.*; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ConfigTree implements TreeModel { + + private ConfigNode.Parent root; + private ConfigNode[] originalChildren; + private List listeners = new ArrayList<>(); + + private StringQuery filter = new StringQuery(); + + public ConfigTree(Object config) { + this.root = ConfigNode.rootingFrom(null, "Root", config, "config"); + this.originalChildren = root.children; + } + + public void setCustom(String name, Object customConfig) { + if (customConfig == null && root.children == originalChildren) return; + ConfigNode[] newChildren; + if (customConfig == null) newChildren = originalChildren; + else { + newChildren = Arrays.copyOf(originalChildren, originalChildren.length + 1); + newChildren[originalChildren.length] = ConfigNode.rootingFrom(root, name, customConfig, "custom"); + } + + this.root.children = newChildren; + SwingUtilities.invokeLater(this::updateListeners); + } + + public void setFilter(String filter) { + this.filter.query = filter; + updateListeners(); + } + + public boolean isUnfiltered() { + return filter.query == null || filter.query.isEmpty(); + } + + private void updateListeners() { + TreeModelEvent event = new TreeModelEvent(this, (TreeNode[]) null, null, null); + for (TreeModelListener listener : listeners) { + listener.treeStructureChanged(event); + } + } + + @Override + public Object getRoot() { + return root; + } + + @Override + public Object getChild(Object parent, int index) { + if (isUnfiltered()) return ((ConfigNode.Parent) parent).children[index]; + return Arrays.stream(((ConfigNode.Parent) parent).children) + .filter(n -> n.isVisible(filter)) + .skip(index) + .findFirst() + .orElse(null); + } + + @Override + public int getChildCount(Object parent) { + return isLeaf(parent) ? 0 : + isUnfiltered() ? ((ConfigNode.Parent) parent).children.length : + (int) Arrays.stream(((ConfigNode.Parent) parent).children) + .filter(n -> n.isVisible(filter)) + .count(); + } + + @Override + public boolean isLeaf(Object node) { + return node instanceof ConfigNode.Leaf; + } + + @Override + public void valueForPathChanged(TreePath path, Object newValue) { + TreeModelEvent event = new TreeModelEvent(this, path); + for (TreeModelListener listener : listeners) { + listener.treeNodesChanged(event); + } + } + + @Override + public int getIndexOfChild(Object parentObj, Object child) { + ConfigNode.Parent parent = (ConfigNode.Parent) parentObj; + int idx = -1; + for (ConfigNode ch : parent.children) { + if (isUnfiltered() || ch.isVisible(filter)) { + idx++; + if (ch == child) return idx; + } + } + return -1; + } + + @Override + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + + @Override + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/Editor.java b/src/main/java/com/github/manolo8/darkbot/config/types/Editor.java new file mode 100644 index 00000000..40b43836 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/Editor.java @@ -0,0 +1,15 @@ +package com.github.manolo8.darkbot.config.types; + +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Editor { + Class value(); + boolean shared() default false; +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/Length.java b/src/main/java/com/github/manolo8/darkbot/config/types/Length.java new file mode 100644 index 00000000..c57361f4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/Length.java @@ -0,0 +1,12 @@ +package com.github.manolo8.darkbot.config.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Length { + int value(); +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/Num.java b/src/main/java/com/github/manolo8/darkbot/config/types/Num.java new file mode 100644 index 00000000..b269587e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/Num.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.config.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Num { + int min() default 0; + int max() default 100; + int step() default 5; +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/Option.java b/src/main/java/com/github/manolo8/darkbot/config/types/Option.java new file mode 100644 index 00000000..c3a3c8b5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/Option.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.config.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Option { + String value() default ""; + String description() default ""; + String key() default ""; +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/Options.java b/src/main/java/com/github/manolo8/darkbot/config/types/Options.java new file mode 100644 index 00000000..8d8ac8b2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/Options.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.config.types; + +import com.github.manolo8.darkbot.config.types.suppliers.OptionList; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Options { + Class> value(); +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/Tag.java b/src/main/java/com/github/manolo8/darkbot/config/types/Tag.java new file mode 100644 index 00000000..de2d6582 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/Tag.java @@ -0,0 +1,12 @@ +package com.github.manolo8.darkbot.config.types; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Tag { + TagDefault value() default TagDefault.UNSET; +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/TagDefault.java b/src/main/java/com/github/manolo8/darkbot/config/types/TagDefault.java new file mode 100644 index 00000000..6fd14131 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/TagDefault.java @@ -0,0 +1,16 @@ +package com.github.manolo8.darkbot.config.types; + +import com.github.manolo8.darkbot.utils.I18n; + +import java.util.Locale; + +public enum TagDefault { + UNSET, + ALL, + NONE; + + @Override + public String toString() { + return I18n.get("players.tag_default." + name().toLowerCase(Locale.ROOT)); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ApiSupplier.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ApiSupplier.java new file mode 100644 index 00000000..d48c8a6f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ApiSupplier.java @@ -0,0 +1,41 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import java.util.Arrays; +import java.util.List; + +public class ApiSupplier extends OptionList { + + private static final List APIS = Arrays.asList( + "Darkbot browser (Legacy)", + "Darkflash API (new)", + "Darkboat API (newer & better)", + "NativeAPI (Linux, unreleased)", + "No-operation API"); + private static final List TOOLTIPS = Arrays.asList( + "Legacy browser, manual login, by Manolo8", + "New API, no browser, auto login, by Manolo8", + "Newer API, no browser, auto login, by Punisher", + "WIP API, many different implementations by zBlock, wakatoa & Tanoshizo. None publicly released", + "API that will do nothing. Useful for testing, default if error occurs loading another API. By Popcorn"); + + @Override + public Integer getValue(String text) { + return Math.max(APIS.indexOf(text), 0); + } + + @Override + public String getTooltipFromVal(Integer value) { + return TOOLTIPS.get(value); + } + + @Override + public String getText(Integer value) { + return APIS.get(value); + } + + @Override + public List getOptions() { + return APIS; + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/DisplayFlag.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/DisplayFlag.java new file mode 100644 index 00000000..c476eeca --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/DisplayFlag.java @@ -0,0 +1,57 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import com.github.manolo8.darkbot.utils.I18n; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public enum DisplayFlag { + USERNAMES, HERO_NAME, HP_SHIELD_NUM, ZONES, STATS_AREA, GROUP_AREA, GROUP_NAMES, BOOSTER_AREA, SORT_BOOSTERS; + + public String getName() { + return I18n.getOrDefault("config.bot_settings.display.toggle." + name().toLowerCase(Locale.ROOT), name()); + } + + public String getShortName() { + return I18n.getOrDefault("config.bot_settings.display.toggle." + name().toLowerCase(Locale.ROOT) + ".short", name()); + } + + public String getDescription() { + return I18n.getOrDefault("config.bot_settings.display.toggle." + name().toLowerCase(Locale.ROOT) + ".desc", null); + } + + public static class Supplier extends OptionList { + private static final List OPTIONS = + Arrays.stream(DisplayFlag.values()).map(DisplayFlag::getName).collect(Collectors.toList()); + + @Override + public DisplayFlag getValue(String text) { + for (DisplayFlag df : DisplayFlag.values()) { + if (df.getName().equals(text)) return df; + } + return null; + } + + @Override + public String getText(DisplayFlag value) { + return value.getName(); + } + + @Override + public String getShortText(DisplayFlag value) { + return value.getShortName(); + } + + @Override + public String getTooltipFromVal(DisplayFlag value) { + return value.getDescription(); + } + + @Override + public List getOptions() { + return OPTIONS; + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/LanguageSupplier.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/LanguageSupplier.java new file mode 100644 index 00000000..8d88638e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/LanguageSupplier.java @@ -0,0 +1,31 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import com.github.manolo8.darkbot.gui.utils.Strings; +import com.github.manolo8.darkbot.utils.I18n; + +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public class LanguageSupplier extends OptionList { + + private static final List LOCALES = I18n.SUPPORTED_LOCALES; + private static final List LOCALE_NAMES = LOCALES.stream() + .map(Locale::getDisplayName).map(Strings::capitalize).collect(Collectors.toList()); + + @Override + public Locale getValue(String text) { + return LOCALES.get(LOCALE_NAMES.indexOf(text)); + } + + @Override + public String getText(Locale value) { + return Strings.capitalize(value.getDisplayName()); + } + + @Override + public List getOptions() { + return LOCALE_NAMES; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ModuleSupplier.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ModuleSupplier.java new file mode 100644 index 00000000..4d45dc9f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ModuleSupplier.java @@ -0,0 +1,65 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.modules.TemporalModule; + +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + +public class ModuleSupplier extends OptionList { + private static Set INSTANCES = Collections.newSetFromMap(new WeakHashMap<>()); + private static Map> MODULES_BY_ID; + private static List MODULE_NAMES; + + public static void updateModules(Map> modules) { + ModuleSupplier.MODULES_BY_ID = modules; + MODULE_NAMES = MODULES_BY_ID.values().stream() + .filter(m -> !TemporalModule.class.isAssignableFrom(m.getClazz())) + .map(FeatureDefinition::getName).collect(Collectors.toList()); + INSTANCES.forEach(model -> { + ListDataEvent ev = new ListDataEvent(model, ListDataEvent.CONTENTS_CHANGED, 0, MODULES_BY_ID.size()); + Arrays.stream(model.dataListeners.getListeners(ListDataListener.class)) + .forEach(listener -> listener.contentsChanged(ev)); + }); + } + + public ModuleSupplier() { + INSTANCES.add(this); + } + + @Override + public String getValue(String name) { + //noinspection StringEquality + return MODULES_BY_ID.entrySet() + .stream() + .filter(e -> e.getValue().getName() == name) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + } + + @Override + public String getTooltipFromVal(String id) { + FeatureDefinition feature = MODULES_BY_ID.get(id); + return feature == null ? null : feature.getDescription(); + } + + @Override + public String getText(String id) { + FeatureDefinition feature = MODULES_BY_ID.get(id); + return feature == null ? null : feature.getName(); + } + + @Override + public List getOptions() { + return MODULE_NAMES; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/OptionList.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/OptionList.java new file mode 100644 index 00000000..0c5d82ba --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/OptionList.java @@ -0,0 +1,47 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import javax.swing.*; +import javax.swing.event.EventListenerList; +import javax.swing.event.ListDataListener; +import java.util.List; + +public abstract class OptionList implements ComboBoxModel { + EventListenerList dataListeners = new EventListenerList(); + + public abstract T getValue(String text); + public abstract String getText(T value); + public String getTooltip(String text) { + return getTooltipFromVal(getValue(text)); + } + public String getTooltipFromVal(T value) { + return null; + } + public String getShortText(T value) { + return getText(value); + } + public abstract List getOptions(); + + private Object selectedItem; + + public void setSelectedItem(Object item) { + selectedItem = item; + } + public Object getSelectedItem() { + return selectedItem; + } + + public int getSize() { + return getOptions().size(); + } + + public String getElementAt(int index) { + return getOptions().get(index); + } + + public void addListDataListener(ListDataListener l) { + dataListeners.add(ListDataListener.class, l); + } + public void removeListDataListener(ListDataListener l) { + dataListeners.remove(ListDataListener.class, l); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/PetGearSupplier.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/PetGearSupplier.java new file mode 100644 index 00000000..08929dec --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/PetGearSupplier.java @@ -0,0 +1,79 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import com.github.manolo8.darkbot.core.manager.PetManager; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class PetGearSupplier extends OptionList { + + public static List GEARS = Collections.emptyList(); + private static final List DEFAULT_OPTIONS = + IntStream.of(1, 2, 4, 6, 10, 12).mapToObj(Gears::getName).collect(Collectors.toList()); + + @Override + public Integer getValue(String text) { + if (text == null) return null; + for (PetManager.Gear gear : GEARS) if (text.equalsIgnoreCase(gear.name)) return gear.id; + //noinspection StringEquality + return Arrays.stream(Gears.values()) + .filter(e -> e.name == text) + .map(Gears::getId) + .findFirst() + .orElse(null); + } + + @Override + public String getText(Integer value) { + for (PetManager.Gear gear : GEARS) if (value == gear.id) return gear.name; + return Gears.getName(value); + } + + @Override + public List getOptions() { + return GEARS.isEmpty() ? DEFAULT_OPTIONS : GEARS.stream().map(g -> g.name).collect(Collectors.toList()); + } + + public enum Gears { + PASSIVE("Passive mode"), + GUARD("Guard mode"), + DESTROYER("Unknown id 3, Destroyer"), + LOOTER("Auto-looter"), + RESOURCE("Auto-resource collector"), + ENEMY_LOCATOR("Enemy locator"), + RESOURCE_LOCATOR("Resource locator"), + TRADER("Cargo trader"), + REPAIR("P.E.T. repairer"), + KAMIKAZE("Kamikaze Detonator"), + COMBO_REPAIR("Combo Ship Repair Gear"), + COMBO_GUARD("Combo Guard Mode Gear"), + DESTROYER_2("Unknown id 13, Destroyer"), + SACRIFICIAL("Sacrificial Flame"), + PET_TARGET("Retargeting P.E.T. Gear"), + HP_LINK("HP Link P.E.T. gear"); + + String name; + Gears(String name) { + this.name = name; + } + + public int getId() { + return ordinal() + 1; + } + + public static String getName(Integer id) { + if (id == null) return null; + if (values().length > id) return values()[id - 1].name; + return "Unknown gear " + id; + } + + @Override + public String toString() { + return name; + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ReviveSpotSupplier.java b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ReviveSpotSupplier.java new file mode 100644 index 00000000..f7031107 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/types/suppliers/ReviveSpotSupplier.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.config.types.suppliers; + +import com.github.manolo8.darkbot.utils.I18n; + +import java.util.Arrays; +import java.util.List; + +public class ReviveSpotSupplier extends OptionList { + + private static final List LOCATIONS = Arrays.asList( + I18n.get("config.general.safety.revive_location.list.base"), + I18n.get("config.general.safety.revive_location.list.portal"), + I18n.get("config.general.safety.revive_location.list.spot")); + + @Override + public Long getValue(String text) { + return (long) (LOCATIONS.indexOf(text) + 1); + } + + @Override + public String getText(Long value) { + return LOCATIONS.get((int) (value - 1)); + } + + @Override + public List getOptions() { + return LOCATIONS; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/utils/ByteArrayToBase64TypeAdapter.java b/src/main/java/com/github/manolo8/darkbot/config/utils/ByteArrayToBase64TypeAdapter.java new file mode 100644 index 00000000..a2018870 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/utils/ByteArrayToBase64TypeAdapter.java @@ -0,0 +1,23 @@ +package com.github.manolo8.darkbot.config.utils; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; +import java.util.Base64; + +public class ByteArrayToBase64TypeAdapter implements JsonSerializer, JsonDeserializer { + public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return Base64.getDecoder().decode(json.getAsString()); + } + + public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(Base64.getEncoder().encodeToString(src)); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/utils/Ignorable.java b/src/main/java/com/github/manolo8/darkbot/config/utils/Ignorable.java new file mode 100644 index 00000000..52ae3e0c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/utils/Ignorable.java @@ -0,0 +1,10 @@ +package com.github.manolo8.darkbot.config.utils; + +public interface Ignorable { + boolean ignore(); + + /** + * @return true to write as null, false not to write (for objects inside an array). + */ + boolean writeAsNull(); +} diff --git a/src/main/java/com/github/manolo8/darkbot/config/utils/SpecialTypeAdapter.java b/src/main/java/com/github/manolo8/darkbot/config/utils/SpecialTypeAdapter.java new file mode 100644 index 00000000..4f14f68d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/config/utils/SpecialTypeAdapter.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.config.utils; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +public class SpecialTypeAdapter implements TypeAdapterFactory { + public TypeAdapter create(Gson gson, TypeToken type) { + + final TypeAdapter delegate = gson.getDelegateAdapter(this, type); + + return new TypeAdapter() { + public void write(JsonWriter out, T value) throws IOException { + if (value instanceof Ignorable && ((Ignorable) value).ignore()) { + // For named fields we are required to write a null, which will remove the field name. + // For objects inside arrays we can simply not write anything and the builder will handle it. + if (((Ignorable) value).writeAsNull()) delegate.write(out, null); + } else delegate.write(out, value); + } + public T read(JsonReader in) throws IOException { + return delegate.read(in); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/BotInstaller.java b/src/main/java/com/github/manolo8/darkbot/core/BotInstaller.java index 35cad2f3..a06eee05 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/BotInstaller.java +++ b/src/main/java/com/github/manolo8/darkbot/core/BotInstaller.java @@ -3,163 +3,113 @@ import com.github.manolo8.darkbot.core.itf.Manager; import com.github.manolo8.darkbot.core.utils.Lazy; -import java.util.ArrayList; -import java.util.List; - import static com.github.manolo8.darkbot.Main.API; public class BotInstaller { - - public static final byte[] bytesToMainApplication = new byte[]{ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 - }; - - private static final byte[] bytesToSettings = new byte[]{ - 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5 - }; - - private final List managers; + private static final byte[] bytesToMainApplication = + new byte[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}; + + private static final byte[] bytesToSettings = + new byte[]{0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5}; + + public final Lazy invalid = new Lazy<>(true); + public final Lazy mainApplicationAddress = new Lazy<>(); + public final Lazy mainAddress = new Lazy<>(); + public final Lazy screenManagerAddress = new Lazy<>(); + public final Lazy guiManagerAddress = new Lazy<>(); + public final Lazy heroInfoAddress = new Lazy<>(); + public final Lazy settingsAddress = new Lazy<>(); public static int SEP; - public final Lazy invalid; - - public final Lazy mainApplicationAddress; - - public final Lazy mainAddress; - - public final Lazy screenManagerAddress; - public final Lazy guiManagerAddress; - - public final Lazy userDataAddress; - public final Lazy settingsAddress; - - private long timer; - public BotInstaller() { - this.managers = new ArrayList<>(); - - this.mainApplicationAddress = new Lazy<>(); - this.mainAddress = new Lazy<>(); - this.screenManagerAddress = new Lazy<>(); - this.guiManagerAddress = new Lazy<>(); - this.userDataAddress = new Lazy<>(); - this.settingsAddress = new Lazy<>(); - - this.invalid = new Lazy<>(true); + public BotInstaller(Manager... managers) { this.invalid.add(value -> { if (value) timer = System.currentTimeMillis(); }); - } - public void add(Manager manager) { - this.managers.add(manager); - } - - public void init() { - for (Manager manager : managers) { - manager.install(this); - } + for (Manager manager : managers) manager.install(this); } public boolean isInvalid() { - if (invalid.value) return true; - - if (API.readMemoryLong(mainApplicationAddress.value + 1344) == mainAddress.value) { - - checkUserData(); - - return false; - } else { - invalid.send(true); + if (invalid.get()) { + checkInvalid(); + invalid.send(tryInstall()); return true; } - } - public void verify() { - if (install0()) { - invalid.send(false); - } else { - invalid.send(true); + if (API.readMemoryLong(mainApplicationAddress.get() + 1344) == mainAddress.get()) { + if (heroInfoAddress.get() == 0) checkUserData(); + return false; } + + invalid.send(true); + return true; } private void checkUserData() { - if (userDataAddress.value != 0) return; - - int id = API.readMemoryInt(API.readMemoryLong(screenManagerAddress.value + 240) + 56); - + int id = API.readMemoryInt(API.readMemoryLong(screenManagerAddress.get() + 240) + 56); if (id == 0) return; - long[] address = API.queryMemoryInt(id, 100); - + long[] address = API.queryMemoryInt(id, 10); for (long value : address) { - - int level = API.readMemoryInt(value + 4); - int speed = API.readMemoryInt(value + 8); - int bool = API.readMemoryInt(value + 12); - - if (level >= 0 && level <= 32 && speed > 50 && speed < 2000 && (bool == 1 || bool == 2)) { - userDataAddress.send(value - 48); + int level = API.readMemoryInt(value + 4); + int speed = API.readMemoryInt(value + 8); + int bool = API.readMemoryInt(value + 12); + int val = API.readMemoryInt(value + 16); + int cargo = API.readMemoryInt(API.readMemoryLong(value - 48 + 240) + 40); + int maxCargo = API.readMemoryInt(API.readMemoryLong(value - 48 + 248) + 40); + + if (level >= 0 && level <= 32 + && speed > 50 && speed < 2000 + && (bool == 1 || bool == 2) + && val == 0 + && cargo >= 0 + && maxCargo >= 100 && maxCargo < 100_000) { + heroInfoAddress.send(value - 48); break; } - } - } - private boolean install0() { - - if (!API.isValid()) return false; - - long[] address = API.queryMemory(bytesToMainApplication, 1); - - checkInvalid(); - - if (address.length != 1) return false; - - mainApplicationAddress.send(address[0] - 228); - - address = API.queryMemory(bytesToSettings, 1); + /** + * Attempts to install, returns if the bot is still invalid + * @return True if invalid and should retry, false otherwise. + */ + private boolean tryInstall() { + if (!API.isValid()) return true; + long[] query; + long temp; - if (address.length != 1) return false; + if ((query = API.queryMemory(bytesToMainApplication, 1)).length != 1) return true; + this.mainApplicationAddress.send(query[0] - 228); + BotInstaller.SEP = API.readMemoryInt(mainApplicationAddress.get() + 4); - settingsAddress.send(address[0] - 237); + if ((query = API.queryMemory(bytesToSettings, 1)).length != 1) return true; + this.settingsAddress.send(query[0] - 237); - SEP = API.readMemoryInt(mainApplicationAddress.value + 4); + if ((temp = API.readMemoryLong(mainApplicationAddress.get() + 1344)) == 0) return true; + this.mainAddress.send(temp); - long temp = API.readMemoryLong(mainApplicationAddress.value + 1344); + if ((temp = API.readMemoryLong(mainAddress.get() + 504)) == 0) return true; + this.screenManagerAddress.send(temp); - if (temp == 0) return false; - - mainAddress.send(temp); - - temp = API.readMemoryLong(mainAddress.value + 504); - - if (temp == 0) return false; - - screenManagerAddress.send(temp); - - temp = API.readMemoryLong(mainAddress.value + 512); - - if (temp == 0) return false; - - guiManagerAddress.send(temp); + if ((temp = API.readMemoryLong(mainAddress.get() + 512)) == 0) return true; + this.guiManagerAddress.send(temp); //reset address - userDataAddress.send(0L); - - return true; + this.heroInfoAddress.send(0L); + return false; } private void checkInvalid() { - if (System.currentTimeMillis() - timer > 180000) { - API.refresh(); - timer = System.currentTimeMillis(); - } - } + if (timer == 0 || System.currentTimeMillis() - timer < 180000) return; + API.handleRefresh(); + timer = System.currentTimeMillis(); + System.out.println("Triggering refresh: bot installer was invalid for too long"); + } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/DarkBotAPI.java b/src/main/java/com/github/manolo8/darkbot/core/DarkBotAPI.java index f70d3358..a803137c 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/DarkBotAPI.java +++ b/src/main/java/com/github/manolo8/darkbot/core/DarkBotAPI.java @@ -1,33 +1,17 @@ package com.github.manolo8.darkbot.core; -import java.nio.charset.StandardCharsets; - public class DarkBotAPI { static { - System.loadLibrary("DarkBot"); - } - - public void createWindow() { - - Thread thread = new Thread(() -> { - createWindow0(); - System.exit(0); - }); - - thread.start(); + System.loadLibrary("lib/DarkBot"); } - private native void createWindow0(); + public native void createWindow0(); public native boolean isValid(); - public native void mousePress(int x, int y); - public native void mouseMove(int x, int y); - public native void mouseRelease(int x, int y); - public native void mouseClick(int x, int y); public native void keyboardClick(char btn); @@ -40,27 +24,10 @@ public void createWindow() { public native boolean readMemoryBoolean(long address); - public String readMemoryString(long address) { - - int flags = readMemoryInt(address + 36); - int width = (flags & 0x00000001); - int size = readMemoryInt(address + 32) << width; - int type = (flags & 0x00000006) >> 1; - - if (size > 256 || size < 0) return "ERROR"; - - byte[] bytes; - - if (type == 2) - bytes = readMemory(readMemoryLong(readMemoryLong(address + 24) + 16) + readMemoryInt(address + 16), size); - else - bytes = readMemory(readMemoryLong(address + 16), size); - - return width == 0 ? new String(bytes, StandardCharsets.ISO_8859_1) : new String(bytes, StandardCharsets.UTF_16LE); - } - public native byte[] readMemory(long address, int length); + public native void writeMemoryDouble(long address, double value); + public native void writeMemoryLong(long address, long value); public native void writeMemoryInt(long address, int value); @@ -71,11 +38,6 @@ public String readMemoryString(long address) { public native long[] queryMemory(byte[] query, int maxQuantity); - public native void setDrawing(boolean drawing); - - public native void setVisible(boolean visible); - public native void refresh(); - public native void forceVisible(boolean value); } diff --git a/src/main/java/com/github/manolo8/darkbot/core/DarkFlash.java b/src/main/java/com/github/manolo8/darkbot/core/DarkFlash.java new file mode 100644 index 00000000..b896ec6f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/DarkFlash.java @@ -0,0 +1,43 @@ +package com.github.manolo8.darkbot.core; + +public class DarkFlash { + + static { + System.loadLibrary("lib/DarkFlash"); + } + + public native void setCookie(String url, String cookie); + + public native void loadSWF(String preloader, String params, String url); + + public native void reloadSWF(); + + public native void mousePress(int x, int y); + + public native void keyPress(char btn); + + public native double readMemoryDouble(long address); + + public native long readMemoryLong(long address); + + public native int readMemoryInt(long address); + + public native byte[] readMemory(long address, int length); + + public native void writeMemoryDouble(long address, double value); + + public native void writeMemoryLong(long address, long value); + + public native void writeMemoryInt(long address, int value); + + public native long[] queryMemoryInt(int value, int maxQuantity); + + public native long[] queryMemoryLong(long value, int maxQuantity); + + public native long[] queryMemory(byte[] query, int maxQuantity); + + public native void setVisible(boolean flag); + + public native void setRender(boolean flag); + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java b/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java new file mode 100644 index 00000000..79e0b395 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/IDarkBotAPI.java @@ -0,0 +1,78 @@ +package com.github.manolo8.darkbot.core; + +public interface IDarkBotAPI { + + void createWindow(); + void setSize(int width, int height); + + boolean isValid(); + long getMemoryUsage(); + + void mouseMove(int x, int y); + + void mouseClick(int x, int y); + + void keyboardClick(char btn); + void rawKeyboardClick(char btn); + + default void keyboardClick(Character ch) { + if (ch != null) keyboardClick((char) ch); + } + + void sendText(String string); // There's currently no working implementation of this. + + double readMemoryDouble(long address); + + default double readMemoryDouble(long address, int... offsets) { + for (int i = 0; i < offsets.length - 1; i++) address = readMemoryLong(address + offsets[i]); + return readMemoryDouble(address + offsets[offsets.length - 1]); + } + + long readMemoryLong(long address); + + default long readMemoryLong(long address, int... offsets) { + for (int offset : offsets) address = readMemoryLong(address + offset); + return address; + } + + int readMemoryInt(long address); + + default int readMemoryInt(long address, int... offsets) { + for (int i = 0; i < offsets.length - 1; i++) address = readMemoryLong(address + offsets[i]); + return readMemoryInt(address + offsets[offsets.length - 1]); + } + + boolean readMemoryBoolean(long address); + + default boolean readMemoryBoolean(long address, int... offsets) { + for (int i = 0; i < offsets.length - 1; i++) address = readMemoryLong(address + offsets[i]); + return readMemoryBoolean(address + offsets[offsets.length - 1]); + } + + String readMemoryString(long address); + + default String readMemoryString(long address, int... offsets) { + for (int offset : offsets) address = readMemoryLong(address + offset); + return readMemoryString(address); + } + + byte[] readMemory(long address, int length); + + void writeMemoryDouble(long address, double value); + + void writeMemoryLong(long address, long value); + + void writeMemoryInt(long address, int value); + + long[] queryMemoryInt(int value, int maxQuantity); + + long[] queryMemoryLong(long value, int maxQuantity); + + long[] queryMemory(byte[] query, int maxQuantity); + + void setVisible(boolean visible); + void setMinimized(boolean visible); + + void handleRefresh(); + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/ApiAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/ApiAdapter.java new file mode 100644 index 00000000..2dd06e12 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/ApiAdapter.java @@ -0,0 +1,96 @@ +package com.github.manolo8.darkbot.core.api; + +import com.github.manolo8.darkbot.core.IDarkBotAPI; +import com.sun.jna.platform.win32.User32; +import com.sun.jna.platform.win32.WinDef; + +import java.awt.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public abstract class ApiAdapter implements IDarkBotAPI { + + protected static final User32 USER_32 = User32.INSTANCE; + + protected volatile WinDef.HWND window; + protected volatile WinDef.HWND flash; + + private int initialWidth, initialHeight; // Prefered sizes set by setSize + + @Override + public String readMemoryString(long address) { + int flags = readMemoryInt(address + 36); + int width = (flags & 0x00000001); + int size = readMemoryInt(address + 32) << width; + int type = (flags & 0x00000006) >> 1; + + if (size > 1024 || size < 0) return "ERROR"; + + byte[] bytes; + + if (type == 2) + bytes = readMemory(readMemoryLong(readMemoryLong(address + 24) + 16) + readMemoryInt(address + 16), size); + else + bytes = readMemory(readMemoryLong(address + 16), size); + + return width == 0 ? new String(bytes, StandardCharsets.ISO_8859_1) : new String(bytes, StandardCharsets.UTF_16LE); + } + + private int x, y, w, h; + public void setVisible(boolean visible) { + if (!visible) { + WinDef.RECT rect = new WinDef.RECT(); + USER_32.GetWindowRect(window, rect); + x = rect.left; + y = rect.top; + w = Math.abs(rect.right - rect.left); + h = Math.abs(rect.bottom - rect.top); + } + int minX = Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) + .mapToInt(g -> g.getDefaultConfiguration().getBounds().x).min().orElse(0); + + USER_32.MoveWindow(window, visible ? x : minX - w - 100, y, w, h, true); + if (visible) USER_32.SetForegroundWindow(window); + } + + public void setMinimized(boolean visible) { + this.setVisible(visible); + } + + public void rawKeyboardClick(char ch) { + keyboardClick(ch); + } + public void sendText(String str) {} + public long getMemoryUsage() { + return 0L; + } + public int getVersion() { + return 0; + } + + public void setSize(int width, int height) { + if (width == -1 && height == -1) { + width = this.initialWidth; + height = this.initialHeight; + } + if (window == null) { + this.initialWidth = width; + this.initialHeight = height; + return; + } + + WinDef.RECT rect = new WinDef.RECT(); + USER_32.GetWindowRect(window, rect); + x = rect.left; + y = rect.top; + w = width; + h = height; + int minX = Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) + .mapToInt(g -> g.getDefaultConfiguration().getBounds().x).min().orElse(0); + + + USER_32.MoveWindow(window, x > minX ? x : minX - w - 100, y, w, h, true); + } + +} + diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java new file mode 100644 index 00000000..363dd138 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBoatAdapter.java @@ -0,0 +1,140 @@ +package com.github.manolo8.darkbot.core.api; + +import com.github.manolo8.darkbot.utils.login.LoginData; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import eu.darkbot.api.DarkBoat; + +public class DarkBoatAdapter extends ApiAdapter { + private final LoginData loginData; + private final DarkBoat API = new DarkBoat(); + + public DarkBoatAdapter() { + this.loginData = LoginUtils.performUserLogin(); + } + + @Override + public void createWindow() { + String url = "https://" + loginData.getUrl() + "/", + sid = "dosid=" + loginData.getSid(); + + API.setData(url, sid, loginData.getPreloaderUrl(), loginData.getParams()); + Thread apiThread = new Thread(API::createWindow); + apiThread.setDaemon(true); + apiThread.start(); + } + + @Override + public void setSize(int width, int height) { + API.setSize(width, height); + } + + @Override + public void setVisible(boolean visible) { + API.setVisible(visible); + } + + @Override + public void setMinimized(boolean visible) { + API.setMinimized(visible); + } + + @Override + public boolean isValid() { + return API.isValid(); + } + + @Override + public long getMemoryUsage() { + return API.getMemoryUsage(); + } + + @Override + public int getVersion() { + return API.getVersion(); + } + + @Override + public void mouseMove(int x, int y) { + API.mouseMove(x, y); + } + + @Override + public void mouseClick(int x, int y) { + API.mouseClick(x, y); + } + + @Override + public void keyboardClick(char btn) { + API.keyClick(Character.toUpperCase(btn)); + } + + @Override + public void rawKeyboardClick(char btn) { + API.keyClick(btn); + } + + @Override + public void sendText(String str) { + API.sendText(str); + } + + @Override + public double readMemoryDouble(long address) { + return API.readDouble(address); + } + + @Override + public long readMemoryLong(long address) { + return API.readLong(address); + } + + @Override + public int readMemoryInt(long address) { + return API.readInt(address); + } + + @Override + public boolean readMemoryBoolean(long address) { + return API.readBoolean(address); + } + + @Override + public byte[] readMemory(long address, int length) { + return API.readBytes(address, length); + } + + @Override + public void writeMemoryDouble(long address, double value) { + API.writeDouble(address, value); + } + + @Override + public void writeMemoryLong(long address, long value) { + API.writeLong(address, value); + } + + @Override + public void writeMemoryInt(long address, int value) { + API.writeInt(address, value); + } + + @Override + public long[] queryMemoryInt(int value, int maxQuantity) { + return API.queryInt(value, maxQuantity); + } + + @Override + public long[] queryMemoryLong(long value, int maxQuantity) { + return API.queryLong(value, maxQuantity); + } + + @Override + public long[] queryMemory(byte[] query, int maxQuantity) { + return API.queryBytes(query, maxQuantity); + } + + @Override + public void handleRefresh() { + API.reload(); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/DarkBotApiAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBotApiAdapter.java new file mode 100644 index 00000000..e42d586f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/DarkBotApiAdapter.java @@ -0,0 +1,109 @@ +package com.github.manolo8.darkbot.core.api; + +import com.github.manolo8.darkbot.core.DarkBotAPI; +import com.github.manolo8.darkbot.utils.Time; + +public class DarkBotApiAdapter extends ApiAdapter { + + private DarkBotAPI API = new DarkBotAPI(); + + @Override + public void createWindow() { + Thread apiThread = new Thread(() -> { + API.createWindow0(); + System.out.println("Browser window exited, exiting"); + System.exit(0); + }, "BotBrowser"); + apiThread.setDaemon(true); + apiThread.start(); + + Thread windowFinder = new Thread(() -> { + while ((window = USER_32.FindWindow("DarkBrowser", "DarkBrowser")) == null || !USER_32.IsWindow(window)) Time.sleep(100); + setSize(-1, -1); + }); + windowFinder.setDaemon(true); + windowFinder.start(); + } + + @Override + public boolean isValid() { + return API.isValid(); + } + + @Override + public void mouseMove(int x, int y) { + API.mouseMove(x, y); + } + + @Override + public void mouseClick(int x, int y) { + API.mouseClick(x, y); + } + + @Override + public void keyboardClick(char btn) { + API.keyboardClick(btn); + } + + @Override + public double readMemoryDouble(long address) { + return API.readMemoryDouble(address); + } + + @Override + public long readMemoryLong(long address) { + return API.readMemoryLong(address); + } + + @Override + public int readMemoryInt(long address) { + return API.readMemoryInt(address); + } + + @Override + public boolean readMemoryBoolean(long address) { + return API.readMemoryBoolean(address); + } + + @Override + public byte[] readMemory(long address, int length) { + return API.readMemory(address, length); + } + + @Override + public void writeMemoryDouble(long address, double value) { + API.writeMemoryDouble(address, value); + } + + @Override + public void writeMemoryLong(long address, long value) { + API.writeMemoryLong(address, value); + } + + @Override + public void writeMemoryInt(long address, int value) { + API.writeMemoryInt(address, value); + } + + @Override + public long[] queryMemoryInt(int value, int maxQuantity) { + return API.queryMemoryInt(value, maxQuantity); + } + + @Override + public long[] queryMemoryLong(long value, int maxQuantity) { + return API.queryMemoryLong(value, maxQuantity); + } + + @Override + public long[] queryMemory(byte[] query, int maxQuantity) { + return API.queryMemory(query, maxQuantity); + } + + @Override + public void handleRefresh() { + USER_32.SetForegroundWindow(window); + API.refresh(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/DarkFlashApiAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/DarkFlashApiAdapter.java new file mode 100644 index 00000000..ed4e8290 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/DarkFlashApiAdapter.java @@ -0,0 +1,146 @@ +package com.github.manolo8.darkbot.core.api; + +import com.github.manolo8.darkbot.core.DarkFlash; +import com.github.manolo8.darkbot.utils.Time; +import com.github.manolo8.darkbot.utils.login.LoginData; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.ptr.IntByReference; + +import javax.swing.*; + +public class DarkFlashApiAdapter extends ApiAdapter { + + private final LoginData loginData; + private final DarkFlash API = new DarkFlash(); + private long willBeValid = System.currentTimeMillis() + 5_000; + + public DarkFlashApiAdapter() { + this.loginData = LoginUtils.performUserLogin(); + } + + @Override + public void createWindow() { + IntByReference parentProcessId = new IntByReference(); + + String url = "https://" + loginData.getUrl() + "/", + sid = "dosid=" + loginData.getSid(); + + API.setCookie(url, sid); + showForm(); + Thread apiThread = new Thread(() -> API.loadSWF(loginData.getPreloaderUrl(), loginData.getParams(), url)); + apiThread.setDaemon(true); + apiThread.start(); + + Thread windowFinder = new Thread(() -> { + while ((window = USER_32.FindWindow(null, "DarkPlayer")) == null || + !USER_32.IsWindow(window) || + USER_32.GetWindowThreadProcessId(window, parentProcessId) == 0 || + parentProcessId.getValue() != Kernel32.INSTANCE.GetCurrentProcessId()) Time.sleep(100); + + API.setVisible(true); + setSize(-1, -1); + flash = USER_32.FindWindowEx(USER_32.FindWindowEx(window, null, null, null), null, null, null); + API.setVisible(true); + API.setRender(true); + }); + windowFinder.setDaemon(true); + windowFinder.start(); + } + + private void showForm() { + // Credits for the dll: + JFrame frame = new JFrame("Darkbot - By Manolo8"); + frame.setVisible(true); + frame.setVisible(false); + } + + public void setMinimized(boolean visible) { + API.setVisible(visible); + } + + @Override + public boolean isValid() { + return willBeValid < System.currentTimeMillis(); + } + + // Forces proguard to include the no-param constructor, needed for mouseMove + static { new WinDef.LRESULT();} + @Override + public void mouseMove(int x, int y) { + USER_32.SendMessage(flash, 0x0200, null, new WinDef.LPARAM(x & 0xFFFF | ((y & 0xFFFF) << 16))); + } + + @Override + public void mouseClick(int x, int y) { + API.mousePress(x, y); + } + + @Override + public void keyboardClick(char btn) { + API.keyPress(btn); + } + + @Override + public double readMemoryDouble(long address) { + return API.readMemoryDouble(address); + } + + @Override + public long readMemoryLong(long address) { + return API.readMemoryLong(address); + } + + @Override + public int readMemoryInt(long address) { + return API.readMemoryInt(address); + } + + @Override + public boolean readMemoryBoolean(long address) { + return this.readMemoryInt(address) == 1; + } + + @Override + public byte[] readMemory(long address, int length) { + return API.readMemory(address, length); + } + + @Override + public void writeMemoryDouble(long address, double value) { + API.writeMemoryDouble(address, value); + } + + @Override + public void writeMemoryLong(long address, long value) { + API.writeMemoryLong(address, value); + } + + @Override + public void writeMemoryInt(long address, int value) { + API.writeMemoryInt(address, value); + } + + @Override + public long[] queryMemoryInt(int value, int maxQuantity) { + return API.queryMemoryInt(value, maxQuantity); + } + + @Override + public long[] queryMemoryLong(long value, int maxQuantity) { + return API.queryMemoryLong(value, maxQuantity); + } + + @Override + public long[] queryMemory(byte[] query, int maxQuantity) { + return API.queryMemory(query, maxQuantity); + } + + @Override + public void handleRefresh() { + willBeValid = System.currentTimeMillis() + 5_000; + API.reloadSWF(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/NativeApiAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/NativeApiAdapter.java new file mode 100644 index 00000000..9a9922eb --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/NativeApiAdapter.java @@ -0,0 +1,135 @@ +package com.github.manolo8.darkbot.core.api; + +import com.github.manolo8.darkbot.utils.login.LoginData; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import eu.darkbot.api.NativeApi; + +public class NativeApiAdapter extends ApiAdapter { + + private final LoginData loginData; + private final NativeApi API = new NativeApi(); + + private static int nextBotId = 0; + private int botId = -1; + + public NativeApiAdapter() { + this.loginData = LoginUtils.performUserLogin(); + } + + public void createWindow() { + botId = nextBotId++; + + if (!API.createBot(botId)) + throw new IllegalStateException("The bot could not successfully setup the browser window"); + + API.sendMessage(botId, Headers.LOGIN, loginData.getUrl().split("\\.")[0], loginData.getSid()); + } + + public boolean isValid() { + return API.isValid(botId); + } + + public void mousePress(int x, int y) { + API.sendMessage(botId, Headers.MOUSE, MouseEvent.DOWN, x, y); + } + + public void mouseMove(int x, int y) { + API.sendMessage(botId, Headers.MOUSE, MouseEvent.MOVE, x, y); + } + + public void mouseRelease(int x, int y) { + API.sendMessage(botId, Headers.MOUSE, MouseEvent.UP, x, y); + } + + public void mouseClick(int x, int y) { + API.mouseClick(botId, 50, x, y); + } + + public void keyboardClick(char btn) { + API.sendMessage(botId, Headers.KEYBOARD, KeyEvent.CLICK, btn); + } + + public void setVisible(boolean visible) { + API.sendMessage(botId, visible ? Headers.SHOW : Headers.HIDE); + } + + public void handleRefresh() { + API.sendMessage(botId, Headers.RELOAD); + } + + public void blockUserInput(boolean block) { + API.sendMessage(botId, Headers.BLOCK_INPUT, block); + } + + public long[] queryMemoryInt(int value, int max) { + return API.queryMemoryInt(botId, value, max); + } + + public long[] queryMemoryLong(long value, int max) { + return API.queryMemoryLong(botId, value, max); + } + + public long[] queryMemory(byte[] query, int max) { + return API.queryMemory(botId, query, max); + } + + public byte[] readMemory(long address, int length) { + return API.readMemory(botId, address, length); + } + + public int readMemoryInt(long address) { + return API.readMemoryInt(botId, address); + } + + public long readMemoryLong(long address) { + return API.readMemoryLong(botId, address); + } + + public double readMemoryDouble(long address) { + return API.readMemoryDouble(botId, address); + } + + public boolean readMemoryBoolean(long address) { + return API.readMemoryBoolean(botId, address); + } + + public void writeMemoryInt(long address, int value) { + API.writeMemoryInt(botId, address, value); + } + + public void writeMemoryLong(long address, long value) { + API.writeMemoryLong(botId, address, value); + } + + public void writeMemoryDouble(long address, double value) { + API.writeMemoryDouble(botId, address, value); + } + + public enum Headers { + LOGIN, RELOAD, MOUSE, KEYBOARD, SHOW, HIDE, BLOCK_INPUT; + + @Override + public String toString() { + return ordinal() + ""; + } + } + + public enum KeyEvent { + DOWN, UP, CLICK; + + @Override + public String toString() { + return (ordinal() + 1) + ""; + } + } + + public enum MouseEvent { + MOVE, DOWN, UP, CLICK; + + @Override + public String toString() { + return (ordinal() + 1) + ""; + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/api/NoopApiAdapter.java b/src/main/java/com/github/manolo8/darkbot/core/api/NoopApiAdapter.java new file mode 100644 index 00000000..510c717e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/api/NoopApiAdapter.java @@ -0,0 +1,89 @@ +package com.github.manolo8.darkbot.core.api; + +/** + * No-operation API adapter. Will do nothing. Useful for testing purposes, and as fallback if no API is loaded. + */ +public class NoopApiAdapter extends ApiAdapter { + @Override + public void createWindow() {} + + @Override + public boolean isValid() { + return false; + } + + @Override + public void mouseMove(int x, int y) { + + } + + @Override + public void mouseClick(int x, int y) { + + } + + @Override + public void keyboardClick(char btn) { + + } + + @Override + public double readMemoryDouble(long address) { + return 0; + } + + @Override + public long readMemoryLong(long address) { + return 0; + } + + @Override + public int readMemoryInt(long address) { + return 0; + } + + @Override + public boolean readMemoryBoolean(long address) { + return false; + } + + @Override + public byte[] readMemory(long address, int length) { + return new byte[0]; + } + + @Override + public void writeMemoryDouble(long address, double value) { + + } + + @Override + public void writeMemoryLong(long address, long value) { + + } + + @Override + public void writeMemoryInt(long address, int value) { + + } + + @Override + public long[] queryMemoryInt(int value, int maxQuantity) { + return new long[0]; + } + + @Override + public long[] queryMemoryLong(long value, int maxQuantity) { + return new long[0]; + } + + @Override + public long[] queryMemory(byte[] query, int maxQuantity) { + return new long[0]; + } + + @Override + public void handleRefresh() { + + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Barrier.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Barrier.java index 6fcc1af3..7f826b1b 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Barrier.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Barrier.java @@ -1,37 +1,29 @@ package com.github.manolo8.darkbot.core.entities; import com.github.manolo8.darkbot.core.itf.Obstacle; -import com.github.manolo8.darkbot.core.utils.Location; +import com.github.manolo8.darkbot.core.manager.MapManager; import com.github.manolo8.darkbot.core.utils.pathfinder.Area; -import static com.github.manolo8.darkbot.Main.API; - public class Barrier - extends Entity + extends Zone implements Obstacle { - private final Area area; + private static final int BARRIER_RADIUS = 60; + private Area area = new Area(0, 0, 0, 0); - public Barrier(int id) { - super(id); - this.area = new Area(0, 0, 0, 0); + public Barrier(int id, long address) { + super(id, address); } @Override public void update() { super.update(); - Location now = locationInfo.now; - - area.minX = now.x - 60; - area.minY = now.y - 60; - area.maxX = now.x + API.readMemoryDouble(address + 232) + 60; - area.maxY = now.y + API.readMemoryDouble(address + 240) + 60; - } - - @Override - public void update(long address) { - super.update(address); + Area zone = getZone(); + area.minX = zone.minX - BARRIER_RADIUS; + area.minY = zone.minY - BARRIER_RADIUS; + area.maxX = zone.maxX + BARRIER_RADIUS; + area.maxY = zone.maxY + BARRIER_RADIUS; } @Override @@ -46,6 +38,7 @@ public boolean isRemoved() { @Override public boolean use() { - return true; + return area.minX > 0 || area.minY > 0 || + area.maxX < MapManager.internalWidth || area.maxY < MapManager.internalHeight; } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/BasePoint.java b/src/main/java/com/github/manolo8/darkbot/core/entities/BasePoint.java new file mode 100644 index 00000000..ffff8a51 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/BasePoint.java @@ -0,0 +1,9 @@ +package com.github.manolo8.darkbot.core.entities; + +public class BasePoint extends Entity { + + public BasePoint(int id, long address) { + super(id, address); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/BattleStation.java b/src/main/java/com/github/manolo8/darkbot/core/entities/BattleStation.java index 15409844..eb2c4943 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/BattleStation.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/BattleStation.java @@ -1,5 +1,6 @@ package com.github.manolo8.darkbot.core.entities; +import com.github.manolo8.darkbot.config.ConfigEntity; import com.github.manolo8.darkbot.core.itf.Obstacle; import com.github.manolo8.darkbot.core.objects.PlayerInfo; import com.github.manolo8.darkbot.core.utils.pathfinder.Area; @@ -10,15 +11,13 @@ public class BattleStation extends Entity implements Obstacle { - public PlayerInfo info; + public PlayerInfo info = new PlayerInfo(); + public Area area = new Area(0, 0, 0, 0); + public int hullId; - private Area area; - - public BattleStation(int id) { + public BattleStation(int id, long address) { super(id); - - this.info = new PlayerInfo(); - this.area = new Area(0, 0, 0, 0); + this.update(address); } @Override @@ -26,14 +25,23 @@ public void update() { super.update(); info.update(); - if (locationInfo.isMoving()) - area.set(locationInfo.now, 300, 300); + if (locationInfo.isMoving()) { + area.set(locationInfo.now, 1200, 1000); + ConfigEntity.INSTANCE.updateSafetyFor(this); + } + } + + @Override + public void removed() { + super.removed(); + ConfigEntity.INSTANCE.updateSafetyFor(this); } @Override public void update(long address) { super.update(address); + hullId = API.readMemoryInt(address + 116); info.update(API.readMemoryLong(address + 120)); } @@ -49,6 +57,11 @@ public boolean isRemoved() { @Override public boolean use() { - return info.isEnemy(); + return hullId > 0 && hullId < 255 && info.isEnemy(); + } + + @Override + public String toString() { + return id + "," + hullId; } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Box.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Box.java index 7d8ac854..cdcec360 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Box.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Box.java @@ -7,27 +7,44 @@ public class Box extends Entity { - protected boolean collected; + private long collectedUntil; + private int retries = 0; + + public String type; public BoxInfo boxInfo; - public Box(int id) { + public Box(int id, long address) { super(id); + this.update(address); } public boolean isCollected() { - return collected; + return removed || System.currentTimeMillis() < collectedUntil; + } + + public void setCollected() { + collectedUntil = System.currentTimeMillis() + getNextWait(); + retries++; } - public void setCollected(boolean collected) { - this.collected = collected; + public int getRetries() { + return retries; + } + + public int getNextWait() { + return retries < 5 ? retries * 2_000 : (retries * 60_000); } @Override public void update(long address) { super.update(address); - long data = traits.elements[0]; + if (traits.size == 0) { + boxInfo = new BoxInfo(); + return; + } + long data = traits.get(0); data = API.readMemoryLong(data + 64); data = API.readMemoryLong(data + 32); @@ -36,12 +53,9 @@ public void update(long address) { data = API.readMemoryLong(data + 16); data = API.readMemoryLong(data + 24); - String type = API.readMemoryString(data); + type = API.readMemoryString(data); - if (type.length() > 5) { - int index; - type = (index = type.indexOf(',')) > 0 ? type.substring(4, index) : type.substring(4); - } + if (type.length() > 5) type = type.split(",")[0].replace("box_", "").replace("_box", ""); boxInfo = ConfigEntity.INSTANCE.getOrCreateBoxInfo(type); } diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Entity.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Entity.java index d23b8b93..486ea89d 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Entity.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Entity.java @@ -1,32 +1,32 @@ package com.github.manolo8.darkbot.core.entities; +import com.github.manolo8.darkbot.Main; import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.manager.EffectManager; import com.github.manolo8.darkbot.core.objects.Clickable; import com.github.manolo8.darkbot.core.objects.LocationInfo; -import com.github.manolo8.darkbot.core.objects.swf.Array; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; + +import java.util.HashMap; +import java.util.Map; import static com.github.manolo8.darkbot.Main.API; public class Entity extends Updatable { + public Main main; + public Map metadata; + public LocationInfo locationInfo = new LocationInfo(); + public Clickable clickable = new Clickable(); + public ObjArray traits = ObjArray.ofVector(true); public int id; - - public LocationInfo locationInfo; - public Clickable clickable; - public boolean removed; - public Array traits; - - public Entity() { - this.locationInfo = new LocationInfo(0); - this.clickable = new Clickable(); - this.traits = new Array(0); - } - - public Entity(int id) { - this(); + public Entity() {} + public Entity(int id) { this.id = id; } + public Entity(int id, long address) { this.id = id; + this.update(address); } public int getId() { @@ -42,8 +42,7 @@ public LocationInfo getLocationInfo() { } public boolean isInvalid(long mapAddress) { - - int id = API.readMemoryInt(address + 56); + int id = API.readMemoryInt(address + 56); long container = API.readMemoryLong(address + 96); return container != mapAddress || this.id != id; @@ -61,14 +60,12 @@ public void update(long address) { this.locationInfo.update(API.readMemoryLong(address + 64)); this.traits.update(API.readMemoryLong(address + 48)); - traits.update(); - - for (int c = 0; c < traits.size; c++) { - long adr = traits.elements[c]; + for (int c = 0; c < traits.getSize(); c++) { + long adr = traits.get(c); - int radius = API.readMemoryInt(adr + 40); + int radius = API.readMemoryInt(adr + 40); int priority = API.readMemoryInt(adr + 44); - int enabled = API.readMemoryInt(adr + 48); + int enabled = API.readMemoryInt(adr + 48); if (radius >= 0 && radius < 4000 && priority > -4 && priority < 1000 && (enabled == 1 || enabled == 0)) { clickable.update(adr); @@ -77,7 +74,35 @@ public void update(long address) { } } + public boolean hasEffect(EffectManager.Effect effect) { + return main != null && main.effectManager.hasEffect(this, effect); + } + + public boolean hasEffect(int effect) { + return main != null && main.effectManager.hasEffect(this, effect); + } + + public void added(Main main) { + this.main = main; + this.removed = false; + } + public void removed() { removed = true; } + + public void setMetadata(String key, Object value) { + if (metadata == null) metadata = new HashMap<>(); + this.metadata.put(key, value); + } + + public Object getMetadata(String key) { + if (metadata == null) return null; + return this.metadata.get(key); + } + + @Override + public String toString() { + return String.valueOf(id); + } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/FakeNpc.java b/src/main/java/com/github/manolo8/darkbot/core/entities/FakeNpc.java new file mode 100644 index 00000000..56f26b1f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/FakeNpc.java @@ -0,0 +1,57 @@ +package com.github.manolo8.darkbot.core.entities; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.core.utils.Location; + +public class FakeNpc extends Npc { + + private long pingAlive = 0; + + public FakeNpc(Main main) { + super(Integer.MIN_VALUE); + super.removed = true; + super.address = -1; + super.main = main; + } + + @Override + public boolean isAttacking(Ship other) { + return false; + } + + @Override + public boolean isAiming(Ship other) { + return false; + } + + @Override + public boolean isInvalid(long mapAddress) { + return false; + } + + @Override public void update() {} + @Override public void update(long address) {} + + @Override + public void removed() { + super.removed(); + pingAlive = 0; + } + + public boolean isPingAlive() { + return pingAlive > System.currentTimeMillis(); + } + + public void set(Location loc, NpcInfo type) { + if (loc == null || (loc.x == 0 && loc.y == 0)) { + if (!isPingAlive()) removed(); + return; + } + removed = false; + pingAlive = System.currentTimeMillis() + 1_000; + locationInfo.updatePosition(loc.x, loc.y); + npcInfo = type; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/MapNpc.java b/src/main/java/com/github/manolo8/darkbot/core/entities/MapNpc.java new file mode 100644 index 00000000..663b67e9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/MapNpc.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.core.entities; + +public class MapNpc extends Npc { + + public MapNpc(int id, long address) { + super(id, address); + } + + @Override + public void update() { + locationInfo.update(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Mine.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Mine.java new file mode 100644 index 00000000..cae9ceb6 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Mine.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.core.entities; + +import static com.github.manolo8.darkbot.Main.API; + +public class Mine extends Entity { + public int typeId; + + public Mine(int id, long address) { + super(id); + this.update(address); + } + + @Override + public void update(long address) { + super.update(address); + + this.typeId = API.readMemoryInt(address + 112); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/NoCloack.java b/src/main/java/com/github/manolo8/darkbot/core/entities/NoCloack.java new file mode 100644 index 00000000..1cc43037 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/NoCloack.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities; + +public class NoCloack extends Zone { + + public NoCloack(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Npc.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Npc.java index 06a8a0d8..0ebec1a4 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Npc.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Npc.java @@ -2,28 +2,37 @@ import com.github.manolo8.darkbot.config.ConfigEntity; import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.core.manager.EffectManager; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import static com.github.manolo8.darkbot.Main.API; public class Npc extends Ship { + private static final NpcInfo INITIAL_NPC_INFO = new NpcInfo(); // Prevent NPE trying to obtain npc info. - public NpcInfo npcInfo; + public NpcInfo npcInfo = INITIAL_NPC_INFO; + public int npcId; + public boolean ish; public Npc(int id) { super(id); } + public Npc(int id, long address) { + super(id); + this.update(address); + } + @Override public void update() { String oldName = playerInfo.username; - super.update(); - //noinspection StringEquality - if (oldName != playerInfo.username) { + npcId = API.readMemoryInt(API.readMemoryLong(address + 192) + 80); + ish = hasEffect(EffectManager.Effect.NPC_ISH); + + if (!oldName.equals(playerInfo.username)) { npcInfo = ConfigEntity.INSTANCE.getOrCreateNpcInfo(playerInfo.username); + npcInfo.npcId = npcId; } } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Pet.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Pet.java index 8e2b46c8..cf34d622 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Pet.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Pet.java @@ -4,8 +4,11 @@ public class Pet extends Ship { - public Pet(int id) { + public Pet() {} + + public Pet(int id, long address) { super(id); + this.update(address); } @Override diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Portal.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Portal.java index 217f02de..49170944 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Portal.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Portal.java @@ -1,27 +1,72 @@ package com.github.manolo8.darkbot.core.entities; +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigEntity; import com.github.manolo8.darkbot.core.objects.Map; -import static com.github.manolo8.darkbot.Main.API; - public class Portal extends Entity { - public Map target; - + public final Map target; + public final int factionId; + private final PortalMatcher matcher; public int type; - public int targetId; + public Portal(int id, int type, int x, int y) { + this(id, type, x, y, null, -1); + } + + public Portal(int searchType, int searchX, int searchY, Map target, int factionId) { + this(-1, searchType, searchX, searchY, target, factionId); + } - public Portal(int id, int targetId) { + public Portal(int id, int searchType, int searchX, int searchY, Map target, int factionId) { super(id); + this.matcher = new PortalMatcher(searchType, searchX, searchY); - this.targetId = targetId; + super.removed = true; + this.target = target; + this.factionId = factionId; + } + + public boolean matches(int x, int y, int type) { + return matcher.matches(x, y, type); } @Override public void update() { super.update(); - type = API.readMemoryInt(address + 112); + type = Main.API.readMemoryInt(address + 112); + if (locationInfo.isMoving()) { + ConfigEntity.INSTANCE.updateSafetyFor(this); + } + } + + @Override + public void added(Main main) { + super.added(main); + ConfigEntity.INSTANCE.updateSafetyFor(this); + } + + @Override + public String toString() { + if (!removed) return id + "(" + locationInfo.now + ")" + type; + else return "(" + matcher.searchX + "," + matcher.searchY + ")" + matcher.searchType; + } + + // Holds the search criteria portals in the star manager + private static class PortalMatcher { + private final int searchType, searchX, searchY; + + PortalMatcher(int searchType, int searchX, int searchY) { + this.searchType = searchType; + this.searchX = searchX; + this.searchY = searchY; + } + + boolean matches(int x, int y, int type) { + return (searchType != -1 && searchType == type) || // By type + (searchX != -1 && searchY != -1 && searchX == x && searchY == y); // By pos + } } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Ship.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Ship.java index 648b2296..dcb47374 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/entities/Ship.java +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Ship.java @@ -3,6 +3,7 @@ import com.github.manolo8.darkbot.core.objects.Health; import com.github.manolo8.darkbot.core.objects.PlayerInfo; import com.github.manolo8.darkbot.core.objects.ShipInfo; +import com.github.manolo8.darkbot.utils.MathUtils; import java.util.HashMap; @@ -12,22 +13,27 @@ public class Ship extends Entity { private static HashMap cacheTimer = new HashMap<>(); - public Health health; - public PlayerInfo playerInfo; - public ShipInfo shipInfo; - public boolean invisible; + public Health health = new Health(); + public PlayerInfo playerInfo = new PlayerInfo(); + public ShipInfo shipInfo = new ShipInfo(); + public boolean invisible; public long timer; + public Ship() {} + public Ship(int id) { super(id); - this.health = new Health(); - this.playerInfo = new PlayerInfo(); - this.shipInfo = new ShipInfo(); - Long temp = cacheTimer.remove(id); + if (temp != null) timer = temp; + } + public Ship(int id, long address) { + super(id); + this.update(address); + + Long temp = cacheTimer.remove(id); if (temp != null) timer = temp; } @@ -35,9 +41,14 @@ public boolean isAttacking(Ship other) { return shipInfo.target == other.address; } + public boolean isAiming(Ship other) { + return MathUtils.angleDiff(shipInfo.angle, locationInfo.now.angle(other.locationInfo.now)) < 0.2; + } + @Override public void update() { super.update(); + clickable.update(); health.update(); shipInfo.update(); diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/Zone.java b/src/main/java/com/github/manolo8/darkbot/core/entities/Zone.java new file mode 100644 index 00000000..89085aa9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/Zone.java @@ -0,0 +1,46 @@ +package com.github.manolo8.darkbot.core.entities; + +import com.github.manolo8.darkbot.core.objects.Point; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import com.github.manolo8.darkbot.core.utils.pathfinder.Area; + +import java.util.ArrayList; +import java.util.List; + +import static com.github.manolo8.darkbot.Main.API; + +public class Zone + extends Entity { + + private final Area area = new Area(0, 0, 0, 0); + private final ObjArray pointsArr = ObjArray.ofVector(true); + private final List points = new ArrayList<>(); + + Zone(int id, long address) { + super(id); + this.update(address); + } + + @Override + public void update() { + super.update(); + + pointsArr.update(API.readMemoryLong(address + 216)); + if (pointsArr.getSize() < 3) return; + pointsArr.sync(points, Point::new, null); + + double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE, minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE; + for (Point p : points) { + minX = Double.min(minX, p.x); + maxX = Double.max(maxX, p.x); + minY = Double.min(minY, p.y); + maxY = Double.max(maxY, p.y); + } + + area.set(minX, minY, maxX, maxY); + } + + public Area getZone() { + return area; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseHangar.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseHangar.java new file mode 100644 index 00000000..b0cd95d9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseHangar.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities.bases; + + +public class BaseHangar extends BaseSpot { + public BaseHangar(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseHeadquarters.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseHeadquarters.java new file mode 100644 index 00000000..67164815 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseHeadquarters.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities.bases; + + +public class BaseHeadquarters extends BaseSpot { + public BaseHeadquarters(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseRefinery.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseRefinery.java new file mode 100644 index 00000000..12f75cc5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseRefinery.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities.bases; + + +public class BaseRefinery extends BaseSpot { + public BaseRefinery(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseRepairStation.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseRepairStation.java new file mode 100644 index 00000000..baaff699 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseRepairStation.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities.bases; + + +public class BaseRepairStation extends BaseSpot { + public BaseRepairStation(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseSpot.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseSpot.java new file mode 100644 index 00000000..16bf4e5f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseSpot.java @@ -0,0 +1,27 @@ +package com.github.manolo8.darkbot.core.entities.bases; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.core.entities.BasePoint; + +/** + * Generic entity for relevant spots of a base + */ +public abstract class BaseSpot extends BasePoint { + public BaseSpot(int id, long address) { + super(id, address); + } + + @Override + public void update() { + super.update(); + + if (locationInfo.isMoving()) ConfigEntity.INSTANCE.updateSafetyFor(this); + } + + @Override + public void removed() { + super.removed(); + ConfigEntity.INSTANCE.updateSafetyFor(this); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseStation.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseStation.java new file mode 100644 index 00000000..ef9542b0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseStation.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities.bases; + + +public class BaseStation extends BaseSpot { + public BaseStation(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseTurret.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseTurret.java new file mode 100644 index 00000000..0ea2438f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/BaseTurret.java @@ -0,0 +1,12 @@ +package com.github.manolo8.darkbot.core.entities.bases; + +import com.github.manolo8.darkbot.core.entities.BasePoint; + +/** + * Small turrets around x-1 and x-8 bases + */ +public class BaseTurret extends BasePoint { + public BaseTurret(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/entities/bases/QuestGiver.java b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/QuestGiver.java new file mode 100644 index 00000000..0975d7b9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/entities/bases/QuestGiver.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.entities.bases; + + +public class QuestGiver extends BaseSpot { + public QuestGiver(int id, long address) { + super(id, address); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/Behaviour.java b/src/main/java/com/github/manolo8/darkbot/core/itf/Behaviour.java new file mode 100644 index 00000000..c70fd2e5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/Behaviour.java @@ -0,0 +1,7 @@ +package com.github.manolo8.darkbot.core.itf; + +public interface Behaviour extends Installable, Tickable { + default void tickBehaviour() { + tick(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/Configurable.java b/src/main/java/com/github/manolo8/darkbot/core/itf/Configurable.java new file mode 100644 index 00000000..d0e9dc23 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/Configurable.java @@ -0,0 +1,5 @@ +package com.github.manolo8.darkbot.core.itf; + +public interface Configurable { + void setConfig(T config); +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/ExtraMenuProvider.java b/src/main/java/com/github/manolo8/darkbot/core/itf/ExtraMenuProvider.java new file mode 100644 index 00000000..8e2670f2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/ExtraMenuProvider.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.core.itf; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.gui.utils.JTitledPopupMenuSeparator; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.event.ActionListener; +import java.util.Collection; + +public interface ExtraMenuProvider { + Collection getExtraMenuItems(Main main); + + /** + * Utility method to create a menu item + * @param key The translation key, or name of the item + * @param listener The action to take when pressed + * @return The created JMenuItem with the action listener + */ + default JMenuItem create(String key, ActionListener listener) { + JMenuItem item = new JMenuItem(I18n.getOrDefault("gui.hamburger_button." + key, key)); + if (listener != null) item.addActionListener(listener); + return item; + } + + default JComponent createSeparator(String key) { + return new JTitledPopupMenuSeparator(I18n.getOrDefault("gui.hamburger_button." + key, key)); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/Installable.java b/src/main/java/com/github/manolo8/darkbot/core/itf/Installable.java new file mode 100644 index 00000000..4296e15c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/Installable.java @@ -0,0 +1,8 @@ +package com.github.manolo8.darkbot.core.itf; + +import com.github.manolo8.darkbot.Main; + +public interface Installable { + void install(Main main); + default void uninstall() {} +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/InstructionProvider.java b/src/main/java/com/github/manolo8/darkbot/core/itf/InstructionProvider.java new file mode 100644 index 00000000..ee1528d1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/InstructionProvider.java @@ -0,0 +1,46 @@ +package com.github.manolo8.darkbot.core.itf; + +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; + +public interface InstructionProvider { + + /** + * Basic instructions, just a string. Will display when the module is set or by user request. + * @return The string to show as instructions + */ + default String instructions() { + return null; + } + + /** + * Advanced instructions, a full component to be shown in the option pane. Allows adding text, buttons, etc. + * @return The JComponent to show as instructions. + */ + default JComponent instructionsComponent() { + return null; + } + + /** + * Called when the user presses to display instructions or the module is loaded. + * You usually would customize via {@link #instructions} or {@link #instructionsComponent} instead. + * @param featureName The name of the feature to display to show instructions for + */ + default void showInstructions(String featureName) { + Object display = instructionsComponent(); + if (display == null) display = instructions(); + if (display == null) return; // If both text & component are null, do nothing + Popups.showMessageAsync(I18n.get("module.instructions.title") + " - " + featureName, + display, JOptionPane.INFORMATION_MESSAGE); + } + + /** + * @return The instructions to show before config tree in config pop-up + */ + default JComponent beforeConfig() { + return null; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/MapChange.java b/src/main/java/com/github/manolo8/darkbot/core/itf/MapChange.java deleted file mode 100644 index 4af9fb64..00000000 --- a/src/main/java/com/github/manolo8/darkbot/core/itf/MapChange.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.manolo8.darkbot.core.itf; - -public interface MapChange { - - void onMapChange(); - -} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/Module.java b/src/main/java/com/github/manolo8/darkbot/core/itf/Module.java index 6cc8a79c..5c711c95 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/itf/Module.java +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/Module.java @@ -2,12 +2,22 @@ import com.github.manolo8.darkbot.Main; -public interface Module { +public interface Module extends Installable, Tickable, RefreshHandler { void install(Main main); boolean canRefresh(); - void tick(); + default void tickModule() { + tick(); + } + + default String status() { + return null; + } + + default String stoppedStatus() { + return null; + } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/NpcExtraProvider.java b/src/main/java/com/github/manolo8/darkbot/core/itf/NpcExtraProvider.java new file mode 100644 index 00000000..c8244bbd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/NpcExtraProvider.java @@ -0,0 +1,7 @@ +package com.github.manolo8.darkbot.core.itf; + +import com.github.manolo8.darkbot.config.NpcExtraFlag; + +public interface NpcExtraProvider { + NpcExtraFlag[] values(); +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/RefreshHandler.java b/src/main/java/com/github/manolo8/darkbot/core/itf/RefreshHandler.java new file mode 100644 index 00000000..41218434 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/RefreshHandler.java @@ -0,0 +1,5 @@ +package com.github.manolo8.darkbot.core.itf; + +public interface RefreshHandler { + boolean canRefresh(); +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/Task.java b/src/main/java/com/github/manolo8/darkbot/core/itf/Task.java new file mode 100644 index 00000000..df19aade --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/Task.java @@ -0,0 +1,7 @@ +package com.github.manolo8.darkbot.core.itf; + +public interface Task extends Installable, Tickable { + default void tickTask() { + tick(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/Tickable.java b/src/main/java/com/github/manolo8/darkbot/core/itf/Tickable.java new file mode 100644 index 00000000..fdd516dd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/Tickable.java @@ -0,0 +1,6 @@ +package com.github.manolo8.darkbot.core.itf; + +public interface Tickable { + void tick(); + default void tickStopped() {} +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/itf/UpdatableAuto.java b/src/main/java/com/github/manolo8/darkbot/core/itf/UpdatableAuto.java new file mode 100644 index 00000000..9fe1a05f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/itf/UpdatableAuto.java @@ -0,0 +1,10 @@ +package com.github.manolo8.darkbot.core.itf; + +public abstract class UpdatableAuto extends Updatable { + + @Override + public void update(long address) { + super.update(address); + update(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/EffectManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/EffectManager.java new file mode 100644 index 00000000..56fcd58c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/EffectManager.java @@ -0,0 +1,77 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.BotInstaller; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.itf.Manager; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.github.manolo8.darkbot.Main.API; + +public class EffectManager implements Manager { + private long mapAddressStatic; + + private ObjArray effectsPtr = ObjArray.ofVector(true); + private Map> effects = new HashMap<>(); + + public EffectManager(Main main) { + } + + @Override + public void install(BotInstaller botInstaller) { + botInstaller.screenManagerAddress.add(value -> mapAddressStatic = value + 256); + } + + public void tick() { + long addr = API.readMemoryLong(API.readMemoryLong(mapAddressStatic), 128, 48); + + effectsPtr.update(addr); + effects.clear(); + + for (int i = 0; i < effectsPtr.getSize(); i++) { + int id = API.readMemoryInt( effectsPtr.get(i) + 0x24); + long entity = API.readMemoryLong(effectsPtr.get(i) + 0x48); + + effects.computeIfAbsent(entity, list -> new ArrayList<>()).add(id); + } + } + + public List getEffects(Entity entity) { + if (entity.address == 0) return Collections.emptyList(); + return effects.getOrDefault(entity.address, Collections.emptyList()); + } + + public boolean hasEffect(Entity entity, Effect effect) { + return hasEffect(entity, effect.id); + } + + public boolean hasEffect(Entity entity, int effect) { + return getEffects(entity).contains(effect); + } + + public enum Effect { + UNDEFINED(-1), + LOCATOR(1), + PET_SPAWN(2), + ENERGY_LEECH(11), + NPC_ISH(16), + DRAW_FIRE(36), + ISH(84), + STICKY_BOMB(56), + POLARITY_POSITIVE(65), + POLARITY_NEGATIVE(66), + INFECTION(85); + + private final int id; + + Effect(int id) { + this.id = id; + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/FacadeManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/FacadeManager.java new file mode 100644 index 00000000..1d6a8ad1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/FacadeManager.java @@ -0,0 +1,75 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.BotInstaller; +import com.github.manolo8.darkbot.core.itf.Manager; +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.objects.facades.*; +import com.github.manolo8.darkbot.core.objects.swf.PairArray; + +import java.util.ArrayList; +import java.util.List; + +import static com.github.manolo8.darkbot.Main.API; + +public class FacadeManager implements Manager { + private final Main main; + private final PairArray commands = PairArray.ofArray(); + private final PairArray proxies = PairArray.ofArray(); + private final PairArray mediators = PairArray.ofArray(); + private final List updatables = new ArrayList<>(); + + public final LogMediator log = registerMediator("LogWindowMediator", new LogMediator()); + public final ChatProxy chat = registerProxy("ChatProxy", new ChatProxy()); + public final StatsProxy stats = registerProxy("StatsProxy", new StatsProxy()); + public final EscortProxy escort = registerProxy("payload_escort", new EscortProxy()); + public final BoosterProxy booster = registerProxy("BoosterProxy", new BoosterProxy()); + public final SlotBarsProxy slotBars = registerProxy("ItemsControlMenuProxy", new SlotBarsProxy()); + public final EternalGateProxy eternalGate = registerProxy("eternal_gate", new EternalGateProxy()); + public final ChrominProxy chrominEvent = registerProxy("chrominEvent", new ChrominProxy()); + + public FacadeManager(Main main) { + this.main = main; + } + + public T registerCommand(String key, T command) { + this.commands.addLazy(key, ((Updatable) command)::update); + updatables.add(command); + return command; + } + + public T registerProxy(String key, T proxy) { + this.proxies.addLazy(key, ((Updatable) proxy)::update); + updatables.add(proxy); + return proxy; + } + + public T registerMediator(String key, T mediator) { + this.mediators.addLazy(key, ((Updatable) mediator)::update); + updatables.add(mediator); + return mediator; + } + + @Override + public void install(BotInstaller botInstaller) { + botInstaller.mainAddress.add(mainAddr -> { + long facade = API.readMemoryLong(mainAddr + 544); + + commands.update(API.readMemoryLong(facade, 0x28, 0x20)); + proxies.update(API.readMemoryLong(facade, 0x38, 0x30)); + mediators.update(API.readMemoryLong(facade, 0x40, 0x38)); + }); + } + + public void tick() { + // Currently commands are not used by the bot and they represent + // a decently big cpu chunk in ticking. Leaving them out reduces tick time significantly. + //commands.update(); + proxies.update(); + mediators.update(); + + for (Updatable updatable : updatables) { + if (updatable.address != 0) updatable.update(); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/GroupManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/GroupManager.java new file mode 100644 index 00000000..47117272 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/GroupManager.java @@ -0,0 +1,198 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.PlayerInfo; +import com.github.manolo8.darkbot.core.objects.Gui; +import com.github.manolo8.darkbot.core.objects.group.Group; +import com.github.manolo8.darkbot.core.objects.group.GroupMember; +import com.github.manolo8.darkbot.core.objects.group.Invite; +import com.github.manolo8.darkbot.core.objects.swf.PairArray; +import com.github.manolo8.darkbot.gui.trail.Line; +import com.github.manolo8.darkbot.utils.Time; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static com.github.manolo8.darkbot.Main.API; + +public class GroupManager extends Gui { + private static final int HEADER_HEIGHT = 26; // Height of the top margin of the group + private static final int BUTTON_HEIGHT = 28; // Height of one invite or button set + private static final int MEMBER_HEIGHT = 48; // Height of one member + + private static final int MARGIN_WIDTH = 10; // Margin from left to first button + private static final int BUTTON_WIDTH = 30; // Width of each button + private static final int INVITE_WIDTH = 130;// Width used by invites, accept or cancel/decline buttons after it + + private final Main main; + private Config.GroupSettings config; + + public Group group; + public boolean pinging; // If the pinging button is enabled & you're ready to ping + public List invites = new ArrayList<>(); + + private final PairArray inviteDict = PairArray.ofDictionary().setAutoUpdatable(true).setIgnoreEmpty(false); + + private long nextAction; + private Runnable pending; + + private final Map pastInvites = new HashMap<>(); + + public GroupManager(Main main) { + this.main = main; + + this.group = new Group(main.hero); + } + + @Override + public void update() { + super.update(); + + if (address == 0 || main.mapManager.eventAddress == 0) return; + + long groupAddress = API.readMemoryLong(API.readMemoryLong(main.mapManager.eventAddress) + 0x48); + if (groupAddress == 0) return; + group.update(API.readMemoryLong(groupAddress + 0x30)); + + pinging = API.readMemoryBoolean(groupAddress + 0x40); + inviteDict.update(API.readMemoryLong(groupAddress + 0x48)); + + inviteDict.sync(invites, () -> new Invite(main.hero), invite -> invite.valid); + } + + public void tick() { + if (group.address == 0) return; + if (nextAction > System.currentTimeMillis()) return; + nextAction = System.currentTimeMillis() + 100; + + this.config = main.config.GROUP; + + tryQueueAcceptInvite(); + tryOpenInvites(); + tryQueueSendInvite(); + + if (pending == null) show(false); + else { + if (!show(true)) return; + pending.run(); + nextAction = System.currentTimeMillis() + 1000; + pending = null; + } + } + + public void tryQueueAcceptInvite() { + if (pending != null || !config.ACCEPT_INVITES || invites.isEmpty() || group.isValid()) return; + + invites.stream() + .filter(in -> in.valid && in.incomming && (config.WHITELIST_TAG == null || + config.WHITELIST_TAG.has(main.config.PLAYER_INFOS.get(in.inviter.id)))) + .findFirst() + .ifPresent(inv -> pending = () -> acceptInvite(inv)); + } + + public void tryOpenInvites() { + if (pending != null || !group.isValid() || !group.isLeader) return; + + if (group.isOpen != config.OPEN_INVITES) pending = () -> click(GroupAction.CAN_INVITE); + } + + public void tryQueueSendInvite() { + if (pending != null || !canInvite() || config.INVITE_TAG == null) return; + + for (PlayerInfo player : main.config.PLAYER_INFOS.values()) { + if (!config.INVITE_TAG.has(player) || + group.getMember(player.userId) != null || + System.currentTimeMillis() < pastInvites.getOrDefault(player.username, 0L)) continue; + + pending = () -> sendInvite(player.username); + break; + } + } + + public void acceptInvite(Invite inv) { + int idx = invites.indexOf(inv); + if (idx >= 0) + clickBtn(MARGIN_WIDTH + INVITE_WIDTH, 0, HEADER_HEIGHT + BUTTON_HEIGHT, idx); + } + + public void sendInvite(String username) { + System.out.println("Sending invite to " + username); + click(MARGIN_WIDTH + (INVITE_WIDTH / 2), getInvitingHeight()); + Time.sleep(75); // This should not be here, but will stay for now + API.sendText(username); + Time.sleep(75); // This should not be here, but will stay for now + click(MARGIN_WIDTH + INVITE_WIDTH + (BUTTON_WIDTH / 2), getInvitingHeight()); + pastInvites.put(username, System.currentTimeMillis() + 60_000); // Wait at least 60s to re-invite + } + + public void kick(int id) { + kick(group.getMember(id)); + } + + public void kick(GroupMember member) { + if (member != null && canKick()) kickUser(group.indexOf(member)); + } + + private void kickUser(int idx) { + if (idx <= 0) return; + + click(GroupAction.REMOVE); + // TODO: wait between clicks + click((int) size.x / 2, (MEMBER_HEIGHT / 2) + idx * MEMBER_HEIGHT); + } + + public boolean canKick() { + return group.isValid() && group.isLeader; + } + + public boolean canInvite() { + return (!group.isValid() || group.isOpen || group.isLeader) && invites.size() + group.size < 8; + } + + private void click(GroupAction action) { + clickBtn(MARGIN_WIDTH, action.idx(group.isLeader), HEADER_HEIGHT + getGroupHeight(), 0); + } + + private int getGroupHeight() { + return Math.max(0, group.size - 1) * MEMBER_HEIGHT + + (invites.size() * BUTTON_HEIGHT) + + (canInvite() ? BUTTON_HEIGHT : 0); + } + + private int getInvitingHeight() { + return HEADER_HEIGHT + (group.isValid() ? getGroupHeight() : BUTTON_HEIGHT) - (BUTTON_HEIGHT / 2); + } + + private int offset(int margin, int offset, int index) { + return margin + (offset * index) + (offset / 2); + } + + private void clickBtn(int marginX, int indexX, + int marginY, int indexY) { + click(offset(marginX, BUTTON_WIDTH, indexX), offset(marginY, BUTTON_HEIGHT, indexY)); + } + + private enum GroupAction { + LEAVE(0), + CAN_INVITE(-1), + PING(1), + FOLLOW(2), + CROWN(-1), + REMOVE(-1); + + private int notLeaderPos; + + GroupAction(int notLeaderPos) { + this.notLeaderPos = notLeaderPos; + } + + public int idx(boolean leader) { + return leader ? ordinal() : notLeaderPos; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/GuiManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/GuiManager.java index b613cc8e..a533c480 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/manager/GuiManager.java +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/GuiManager.java @@ -1,21 +1,32 @@ package com.github.manolo8.darkbot.core.manager; import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.ConfigManager; import com.github.manolo8.darkbot.core.BotInstaller; import com.github.manolo8.darkbot.core.itf.Manager; import com.github.manolo8.darkbot.core.objects.Gui; -import com.github.manolo8.darkbot.core.objects.swf.Dictionary; +import com.github.manolo8.darkbot.core.objects.OreTradeGui; +import com.github.manolo8.darkbot.core.objects.TargetedOfferGui; +import com.github.manolo8.darkbot.core.objects.swf.PairArray; import com.github.manolo8.darkbot.core.utils.ByteUtils; +import com.github.manolo8.darkbot.core.utils.Location; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; import static com.github.manolo8.darkbot.Main.API; public class GuiManager implements Manager { private final Main main; - private final Dictionary guis; + private final PairArray guis = PairArray.ofDictionary(); private long reconnectTime; - private long repairTime; + private long lastDeath = -1; + private long lastRepair; private long validTime; private long repairAddress; @@ -24,10 +35,53 @@ public class GuiManager implements Manager { private long guiAddress; private long mainAddress; - private final Gui lostConnection; - private final Gui connecting; - - public boolean check; + private final List registeredGuis = new ArrayList<>(); + + public final Gui lostConnection = register("lost_connection"); + public final Gui connecting = register("connection"); + public final Gui quests = register("quests"); + public final Gui minimap = register("minimap"); + public final Gui targetedOffers = register("targetedOffers", new TargetedOfferGui()); + public final Gui logout = register("logout"); + public final Gui eventProgress = register("eventProgress"); + public final Gui eternalGate = register("eternal_gate"); + public final PetManager pet; + public final OreTradeGui oreTrade; + public final GroupManager group; + + private static boolean minimapWasOpen; + private static Location previous; + private static long minimapClick; + private LoadStatus checks = LoadStatus.WAITING; + private enum LoadStatus { + WAITING(gm -> { + minimapWasOpen = gm.minimap.visible; + return gm.quests.lastUpdatedIn(5000) && gm.quests.visible; + }), + MISSION_CLOSING(gm -> gm.quests.show(false)), + CLICKING_AMMO(gm -> { + API.keyboardClick(gm.main.config.LOOT.AMMO_KEY); + return true; + }), + OPEN_MINIMAP(gm -> gm.minimap.show(true)), // Ensure minimap shows + CLICK_MINIMAP(gm -> { + previous = gm.main.hero.locationInfo.now.copy(); + minimapClick = System.currentTimeMillis(); + gm.minimap.click(100, 100); + return true; + }), + STOP_MOVE(gm -> { // If drive already moving, ignored, otherwise, move towards previous after 1s + if (minimapClick + 1000 < System.currentTimeMillis()) gm.main.hero.drive.move(previous); + return !gm.main.hero.drive.paths.isEmpty(); + }), + RESET_MINIMAP(gm -> gm.minimap.show(minimapWasOpen)), // Hide if originally hidden + DONE(q -> false); + + Predicate canAdvance; + LoadStatus(Predicate next) { + this.canAdvance = next; + } + } public int deaths; @@ -35,26 +89,36 @@ public GuiManager(Main main) { this.main = main; this.validTime = System.currentTimeMillis(); - this.guis = new Dictionary(0); - this.lostConnection = new Gui(0); - this.connecting = new Gui(0); + this.pet = register("pet", new PetManager(main)); + this.oreTrade = register("ore_trade", new OreTradeGui(main)); + this.group = register("group", new GroupManager(main)); this.main.status.add(value -> validTime = System.currentTimeMillis()); } - @Override - public void install(BotInstaller botInstaller) { + public Gui register(String key) { + return register(key, new Gui()); + } - this.guis.addLazy("lost_connection", lostConnection::update); - this.guis.addLazy("connecting", value -> System.out.println("HAS CONNECTING!")); - this.guis.addLazy("connection", connecting::update); + public T register(String key, T gui) { + @SuppressWarnings("UnnecessaryLocalVariable") + Gui guiFix = gui; // Workaround for a java compiler assertion bug having issues with types + this.guis.addLazy(key, guiFix::update); + this.registeredGuis.add(guiFix); + return gui; + } + @Override + public void install(BotInstaller botInstaller) { botInstaller.screenManagerAddress.add(value -> screenAddress = value); botInstaller.mainAddress.add(value -> mainAddress = value); botInstaller.invalid.add(value -> { - if (!value) validTime = System.currentTimeMillis(); + if (!value) { + validTime = System.currentTimeMillis(); + checks = LoadStatus.WAITING; + } }); botInstaller.guiManagerAddress.add(value -> { @@ -62,35 +126,42 @@ public void install(BotInstaller botInstaller) { guis.update(API.readMemoryLong(guiAddress + 112)); repairAddress = 0; - lostConnection.reset(); - connecting.reset(); - - check = true; + registeredGuis.forEach(Gui::reset); + checks = LoadStatus.WAITING; }); } public void tick() { - guis.update(); - lostConnection.update(); - connecting.update(); + registeredGuis.forEach(Gui::update); + + if (checks != LoadStatus.DONE && checks.canAdvance.test(this)) + checks = LoadStatus.values()[checks.ordinal() + 1]; + targetedOffers.show(false); } private void tryReconnect(Gui gui) { if (System.currentTimeMillis() - reconnectTime > 5000) { reconnectTime = System.currentTimeMillis(); - API.mouseClick(gui.x + 46, gui.y + 180); + gui.click(46, 180); } } - private void tryRevive() { - if (System.currentTimeMillis() - repairTime > 10000) { + private boolean tryRevive() { + if (System.currentTimeMillis() - lastRepair > 10000) { deaths++; - API.writeMemoryLong(repairAddress + 32, main.config.REPAIR_LOCAL + 1); + long respawnId = main.config.GENERAL.SAFETY.REVIVE_LOCATION; + if (!main.repairManager.canRespawn((int)respawnId)) { + respawnId = main.repairManager.getRespawnOptionsIds()[0]; + } + API.writeMemoryLong(repairAddress + 32, respawnId); API.mouseClick(MapManager.clientWidth / 2, (MapManager.clientHeight / 2) + 190); - repairTime = System.currentTimeMillis(); + lastRepair = System.currentTimeMillis(); + if (main.config.MISCELLANEOUS.DRONE_REPAIR_PERCENTAGE != 0) this.main.backpage.checkDronesAfterKill(); + return true; } + return false; } private boolean isInvalidShip() { @@ -100,24 +171,17 @@ private boolean isInvalidShip() { private boolean isDead() { if (repairAddress != 0) { return API.readMemoryBoolean(repairAddress + 40); - } else { - if (isInvalidShip()) { - - long[] values = API.queryMemory(ByteUtils.getBytes(guiAddress, mainAddress), 1); - - if (values.length == 1) - repairAddress = values[0] - 56; - - return false; - } else { - return false; - } + } else if (isInvalidShip()) { + long[] values = API.queryMemory(ByteUtils.getBytes(guiAddress, mainAddress), 1); + if (values.length == 1) repairAddress = values[0] - 56; } + return false; } private void checkInvalid() { - if (System.currentTimeMillis() - validTime > 90 * 1000 + (main.hero.map.id == -1 ? 180 * 1000 : 0)) { - API.refresh(); + if (System.currentTimeMillis() - validTime > 90_000 + (main.hero.map.id == -1 ? 180_000 : 0)) { + System.out.println("Triggering refresh: gui manger was invalid for too long. (Make sure your hp fills up)"); + API.handleRefresh(); validTime = System.currentTimeMillis(); } } @@ -126,7 +190,7 @@ public boolean canTickModule() { if (lostConnection.visible) { //Wait 15 seconds to reconnect - if (lostConnection.lastUpdatedIn(25000)) { + if (lostConnection.lastUpdatedIn(2500)) { tryReconnect(lostConnection); checkInvalid(); } @@ -134,29 +198,47 @@ public boolean canTickModule() { } else if (connecting.visible) { if (connecting.lastUpdatedIn(30000)) { - API.refresh(); + System.out.println("Triggering refresh: connection window stuck for too long"); + API.handleRefresh(); connecting.reset(); } return false; - } else if (isDead()) { + } - tryRevive(); + if (isDead()) { + main.hero.drive.stop(false); - if (deaths >= main.config.MAX_DEATHS) - main.setRunning(false); - else - checkInvalid(); + if (lastDeath == -1) lastDeath = System.currentTimeMillis(); + + if (System.currentTimeMillis() - lastDeath < (main.config.GENERAL.SAFETY.WAIT_BEFORE_REVIVE * 1000) + || !tryRevive()) return false; + lastDeath = -1; + if (deaths >= main.config.GENERAL.SAFETY.MAX_DEATHS) main.setRunning(false); + else checkInvalid(); + + return false; + } else { + lastDeath = -1; + } + + + HeroManager hero = main.hero; + if (System.currentTimeMillis() - lastRepair < main.config.GENERAL.SAFETY.WAIT_AFTER_REVIVE * 1000) { + validTime = System.currentTimeMillis(); return false; - } else if (main.hero.locationInfo.isMoving()) { + } else if (hero.locationInfo.isLoaded() + && (hero.locationInfo.isMoving() || System.currentTimeMillis() - hero.drive.lastMoved > 20 * 1000) + && (hero.health.hpIncreasedIn(30_000) || hero.health.hpDecreasedIn(30_000) || hero.health.hpPercent() == 1 || (hero.hasTarget() && hero.isAttacking(hero.target))) + && (hero.health.shIncreasedIn(30_000) || hero.health.shDecreasedIn(30_000) || hero.health.shieldPercent() == 1 || hero.health.shieldPercent() == 0)) { validTime = System.currentTimeMillis(); } checkInvalid(); - return true; + return main.hero.locationInfo.isLoaded(); } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/HeroManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/HeroManager.java index d7422fdf..e3978e2d 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/manager/HeroManager.java +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/HeroManager.java @@ -1,7 +1,9 @@ package com.github.manolo8.darkbot.core.manager; import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; import com.github.manolo8.darkbot.core.BotInstaller; +import com.github.manolo8.darkbot.core.entities.Npc; import com.github.manolo8.darkbot.core.entities.Pet; import com.github.manolo8.darkbot.core.entities.Portal; import com.github.manolo8.darkbot.core.entities.Ship; @@ -14,10 +16,10 @@ public class HeroManager extends Ship implements Manager { public static HeroManager instance; - private final Main main; + public final Main main; + private final SettingsManager settings; private long staticAddress; - private long settingsAddress; public final Pet pet; public final Drive drive; @@ -27,32 +29,31 @@ public class HeroManager extends Ship implements Manager { public Ship target; public int config; + public int formationId; private long configTime; + private Character formation = null; + private long formationTime; private long portalTime; public HeroManager(Main main) { - super(0); instance = this; - this.main = main; + this.main = super.main = main; + this.settings = main.settingsManager; this.drive = new Drive(this, main.mapManager); - this.pet = new Pet(0); - this.map = new Map(0, "LOADING", false, new Portal[0]); + main.status.add(drive::toggleRunning); + this.pet = new Pet(); + this.map = main.starManager.byId(-1); } @Override public void install(BotInstaller botInstaller) { botInstaller.screenManagerAddress.add(value -> staticAddress = value + 240); - botInstaller.settingsAddress.add(value -> this.settingsAddress = value); } public void tick() { - long address = API.readMemoryLong(staticAddress); - - if (this.address != address) { - update(address); - } + if (this.address != address) update(address); update(); @@ -61,18 +62,13 @@ public void tick() { @Override public void update() { - super.update(); - - pet.update(); - - config = API.readMemoryInt(settingsAddress + 56); + config = settings.config; + formationId = API.readMemoryInt(address, 280, 40, 40); long petAddress = API.readMemoryLong(address + 176); - - if (petAddress != pet.address) { - pet.update(petAddress); - } + if (petAddress != pet.address) pet.update(petAddress); + pet.update(); } @Override @@ -88,34 +84,69 @@ public void setTarget(Ship entity) { this.target = entity; } - public long timeTo(double distance) { - return (long) (distance * 1000 / shipInfo.speed); + public boolean hasTarget() { + return this.target != null && !target.removed; } - public int nextMap() { - return API.readMemoryInt(API.readMemoryInt(settingsAddress + 204)); + public long timeTo(double distance) { + return (long) (distance * 1000 / shipInfo.speed); } public void jumpPortal(Portal portal) { - if (portal.target.id != nextMap() && System.currentTimeMillis() - portalTime > 10000) { + if (portal.removed) return; + if (System.currentTimeMillis() - portalTime > 10000 || (System.currentTimeMillis() - portalTime > 1000 && + map.id == settings.currMap && + (settings.nextMap == -1 || portal.target == null || settings.nextMap != portal.target.id))) { API.keyboardClick('j'); portalTime = System.currentTimeMillis(); } } - public void attackMode() { - if (config != main.config.OFFENSIVE_CONFIG && System.currentTimeMillis() - configTime > 6000) { - API.keyboardClick('c'); - API.keyboardClick(main.config.OFFENSIVE_FORMATION); - configTime = System.currentTimeMillis(); - } + public boolean attackMode() { + return setMode(main.config.GENERAL.OFFENSIVE); } - public void runMode() { - if (config != main.config.RUN_CONFIG && System.currentTimeMillis() - configTime > 6000) { - API.keyboardClick(main.config.RUN_FORMATION); - API.keyboardClick('c'); - configTime = System.currentTimeMillis(); + public boolean attackMode(Npc target) { + if (target == null) return attackMode(); + Config.ShipConfig config = this.main.config.GENERAL.OFFENSIVE; + return setMode(config.CONFIG, target.npcInfo.attackFormation != null ? + target.npcInfo.attackFormation : config.FORMATION); + } + + public boolean runMode() { + return setMode(this.main.config.GENERAL.RUN); + } + + public boolean roamMode() { + return setMode(main.config.GENERAL.ROAM); + } + + public boolean setMode(Config.ShipConfig config) { + return setMode(config.CONFIG, config.FORMATION); + } + + public boolean setMode(int con, Character form) { + int formationCheck = main.config.GENERAL.FORMATION_CHECK; + + if (this.config != con && System.currentTimeMillis() - configTime > 5500L) { + Main.API.keyboardClick('c'); + this.configTime = System.currentTimeMillis(); } + boolean checkFormation = formationCheck > 0 && (System.currentTimeMillis() - formationTime) > formationCheck * 1000; + + if ((this.formation != form || checkFormation) && System.currentTimeMillis() - formationTime > 3500L) { + Main.API.keyboardClick(this.formation = form); + if (formation != null) this.formationTime = System.currentTimeMillis(); + } + return isInMode(con, form); + } + + public boolean isInMode(Config.ShipConfig config) { + return isInMode(config.CONFIG, config.FORMATION); } -} \ No newline at end of file + + public boolean isInMode(int config, Character formation) { + return this.config == config && this.formation == formation; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/MapManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/MapManager.java index 7337f5f2..a8b41514 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/manager/MapManager.java +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/MapManager.java @@ -1,11 +1,19 @@ package com.github.manolo8.darkbot.core.manager; import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.SafetyInfo; +import com.github.manolo8.darkbot.config.ZoneInfo; import com.github.manolo8.darkbot.core.BotInstaller; import com.github.manolo8.darkbot.core.entities.Entity; import com.github.manolo8.darkbot.core.itf.Manager; -import com.github.manolo8.darkbot.core.itf.MapChange; +import com.github.manolo8.darkbot.core.objects.Map; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; import com.github.manolo8.darkbot.core.utils.EntityList; +import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.core.utils.Location; + +import java.util.Set; import static com.github.manolo8.darkbot.Main.API; @@ -17,15 +25,21 @@ public class MapManager implements Manager { private long mapAddressStatic; private long viewAddressStatic; - public long mapAddress; + private long minimapAddressStatic; + private long mapAddress; private long viewAddress; private long boundsAddress; - private long eventAddress; + long eventAddress; + + public static int id = -1; + public Lazy mapChange = new Lazy.NoCache<>(); - public static int id; + public ZoneInfo preferred; + public ZoneInfo avoided; + public Set safeties; - public static int internalWidth; - public static int internalHeight; + public static int internalWidth = 21000; + public static int internalHeight = 13500; public static int clientWidth; public static int clientHeight; @@ -34,6 +48,11 @@ public class MapManager implements Manager { public double boundY; public double boundMaxX; public double boundMaxY; + public double width; + public double height; + + private ObjArray minimapLayers = ObjArray.ofVector(true); + public Location pingLocation = null; public MapManager(Main main) { this.main = main; @@ -44,13 +63,15 @@ public MapManager(Main main) { @Override public void install(BotInstaller botInstaller) { - botInstaller.screenManagerAddress.add(value -> { - mapAddressStatic = value + 256; - viewAddressStatic = value + 216; eventAddress = value + 200; + viewAddressStatic = value + 216; + minimapAddressStatic = value + 224; + mapAddressStatic = value + 256; + }); + botInstaller.invalid.add(invalid -> { + if (invalid) entities.clear(); }); - } public void tick() { @@ -58,34 +79,39 @@ public void tick() { if (mapAddress != temp) { update(temp); + } else { + pingLocation = updateMinimap(); + entities.update(); } - entities.update(); - updateBounds(); checkMirror(); } private void update(long address) { - mapAddress = address; internalWidth = API.readMemoryInt(address + 68); internalHeight = API.readMemoryInt(address + 72); - int tempId = API.readMemoryInt(address + 76); + if (internalHeight == 13100) internalHeight = 13500; + int currMap = API.readMemoryInt(address + 76); + boolean switched = currMap != id; + if (switched) { + id = currMap; + main.hero.map = main.starManager.byId(id); + updateAreas(); + } entities.update(address); + if (switched) mapChange.send(main.hero.map); + } - if (tempId != id) { - id = tempId; - main.hero.map = main.starManager.fromId(id); - - if (main.module instanceof MapChange) { - ((MapChange) main.module).onMapChange(); - } - } + public void updateAreas() { + preferred = ConfigEntity.INSTANCE.getOrCreatePreferred(); + avoided = ConfigEntity.INSTANCE.getOrCreateAvoided(); + safeties = ConfigEntity.INSTANCE.getOrCreateSafeties(); } - void checkMirror() { + private void checkMirror() { long temp = API.readMemoryLong(eventAddress) + 4 * 14; if (API.readMemoryBoolean(temp)) { @@ -93,8 +119,7 @@ void checkMirror() { } } - void updateBounds() { - + private void updateBounds() { long temp = API.readMemoryLong(viewAddressStatic); if (viewAddress != temp) { @@ -112,6 +137,34 @@ void updateBounds() { boundY = API.readMemoryDouble(updated + 88); boundMaxX = API.readMemoryDouble(updated + 112); boundMaxY = API.readMemoryDouble(updated + 120); + width = boundMaxX - boundX; + height = boundMaxY - boundY; + } + + private Location updateMinimap() { + long temp = API.readMemoryLong(minimapAddressStatic); // Minimap + double minimapX = API.readMemoryInt(temp + 0xA8); + + temp = API.readMemoryLong(temp + 0xF8); // LayeredSprite + temp = API.readMemoryLong(temp + 0xA8); // Vector + minimapLayers.update(temp); + + for (int i = 0; i < minimapLayers.getSize(); i++) { + long layer = minimapLayers.get(i); // Seems to be offset by 1 for some reason. + long layerIdx = API.readMemoryInt(layer + 0xA8); + + if (layerIdx != Integer.MAX_VALUE) continue; + + long addr = API.readMemoryLong(layer, 0x48, 0x20, 0x18); + int x = API.readMemoryInt(addr + 0x58); + int y = API.readMemoryInt(addr + 0x5C); + + double scale = (internalWidth / minimapX) / 20; + + return new Location(scale * x, scale * y); + } + + return null; } public boolean isTarget(Entity entity) { @@ -124,37 +177,15 @@ public boolean isOutOfMap(double x, double y) { public boolean isCurrentTargetOwned() { long temp = API.readMemoryLong(viewAddressStatic); - temp = API.readMemoryLong(temp + 216); - temp = API.readMemoryLong(temp + 200); - temp = API.readMemoryLong(temp + 48); - return API.readMemoryInt(temp + 40) == 1; - } - - public void translateMouseMove(double x, double y) { - API.mouseMove( - (int) ((x - boundX) / (boundMaxX - boundX) * clientWidth), - (int) ((y - boundY) / (boundMaxY - boundY) * clientHeight) - ); - } - - public void translateMousePress(double x, double y) { - API.mousePress( - (int) ((x - boundX) / (boundMaxX - boundX) * clientWidth), - (int) ((y - boundY) / (boundMaxY - boundY) * clientHeight) - ); - } - - public void translateMouseClick(double x, double y) { - API.mouseClick( - (int) ((x - boundX) / (boundMaxX - boundX) * clientWidth), - (int) ((y - boundY) / (boundMaxY - boundY) * clientHeight) - ); + temp = API.readMemoryLong(temp + 216); // + temp = API.readMemoryLong(temp + 200); // + temp = API.readMemoryLong(temp + 48); // get _target + int lockStatus = API.readMemoryInt(temp + 40); // IntHolder.value + // 1 = selected & owned + // 2 = selected & someone else owns it + // 3 = ? + // 4 = ? + return lockStatus == 1 || lockStatus < 1 || lockStatus > 4; } - public void translateMouseMoveRelease(double x, double y) { - API.mouseRelease( - (int) ((x - boundX) / (boundMaxX - boundX) * clientWidth), - (int) ((y - boundY) / (boundMaxY - boundY) * clientHeight) - ); - } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/MouseManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/MouseManager.java new file mode 100644 index 00000000..c031cf5a --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/MouseManager.java @@ -0,0 +1,75 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.objects.Point; +import com.github.manolo8.darkbot.core.utils.ClickPoint; +import com.github.manolo8.darkbot.core.utils.Location; + +import static com.github.manolo8.darkbot.Main.API; + +public class MouseManager extends Thread { + + private final Object LOCK = new Object(); + private ClickPoint clickPoint = new ClickPoint(0, 0); + private Point point = new Point(); + private volatile boolean waiting = true; + + private final MapManager map; + + public MouseManager(MapManager map) { + super("MouseClicker"); + this.map = map; + setDaemon(true); + start(); + } + + public void clickCenter(boolean single, Location aim) { + ClickPoint clickPoint = pointCenter(aim); + API.mouseClick(clickPoint.x, clickPoint.y); + if (!single) API.mouseClick(clickPoint.x, clickPoint.y); + } + + public void clickLoc(Location loc) { + clickPoint.set((int) loc.x, (int) loc.y); + + point.update(Main.API.readMemoryLong(Main.API.readMemoryLong(map.eventAddress) + 64L)); + ClickPoint click = pointCenter(loc); + + waiting = false; + synchronized (LOCK) { + LOCK.notifyAll(); + } + Main.API.mouseClick(click.x, click.y); + waiting = true; + } + + + private ClickPoint pointCenter(Location aim) { + Location center = new Location(map.boundX + map.width / 2, map.boundY + map.height / 2); + center.toAngle(center, center.angle(aim) + Math.random() * 0.2 - 0.1, 125 + Math.random() * 75); + return pointLoc(center); + } + + private ClickPoint pointLoc(Location loc) { + return new ClickPoint((int) ((loc.x - map.boundX) / (map.width) * (double) MapManager.clientWidth), + (int) ((loc.y - map.boundY) / (map.height) * (double) MapManager.clientHeight)); + } + + public final void run() { + while (true) { + synchronized (this.LOCK) { + if (waiting) { + try { + this.LOCK.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + Main.API.writeMemoryDouble(point.address + 32L, clickPoint.x); + Main.API.writeMemoryDouble(point.address + 40L, clickPoint.y); + } + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/PetManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/PetManager.java new file mode 100644 index 00000000..08d5724d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/PetManager.java @@ -0,0 +1,287 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.NpcExtra; +import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.config.types.suppliers.PetGearSupplier; +import com.github.manolo8.darkbot.core.entities.Npc; +import com.github.manolo8.darkbot.core.entities.Pet; +import com.github.manolo8.darkbot.core.entities.Ship; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.Gui; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import static com.github.manolo8.darkbot.Main.API; + +public class PetManager extends Gui { + + private static final int MAIN_BUTTON_X = 30, + MODULES_X_MAX = 260, + MODULE_Y = 135, + MODULE_Y_OFFSET = 17, + MODULE_HEIGHT = 23, + SUBMODULE_HEIGHT = 22; + + private final Main main; + private final List ships; + private final Pet pet; + + private long togglePetTime, selectModuleTime; + private long activeUntil; + private Ship target; + private boolean enabled = false; + + private final ObjArray guiSprites = ObjArray.ofSprite(); + + private final ObjArray modulesArr = ObjArray.ofArrObj(); + private final ObjArray currentArr = ObjArray.ofArrObj(); + private final ObjArray currSpriteWrapper = ObjArray.ofSprite(); + private final ObjArray currSprite = ObjArray.ofSprite(); + + private final ObjArray gearsArr = ObjArray.ofArrObj(); + private final List gearList = new ArrayList<>(); + + private final ObjArray locatorWrapper = ObjArray.ofArrObj(), locatorNpcList = ObjArray.ofArrObj(); + private final List locatorList = new ArrayList<>(); + + + private ModuleStatus selection = ModuleStatus.NOTHING; + private Gear currentModule; // The Module used, like Passive mode, kamikaze, or enemy locator + private Gear currentSubModule;// The submodule used, like an npc inside enemy locator. + private long validUntil; + private NpcInfo selectedNpc; + + private Integer gearOverride = null; + private long gearOverrideTime = 0; + + private enum ModuleStatus { + NOTHING, + DROPDOWN, + SUB_DROPDOWN, + SELECTED + } + + PetManager(Main main) { + this.main = main; + this.ships = main.mapManager.entities.ships; + this.pet = main.hero.pet; + + PetGearSupplier.GEARS = gearList; + } + + public void tick() { + if (!main.isRunning() || !main.config.PET.ENABLED) return; + if (active() != enabled) { + if (show(true)) clickToggleStatus(); + return; + } + if (!enabled) { + show(false); + return; + } + updatePetTarget(); + int moduleId = main.config.PET.MODULE_ID; + if (main.config.PET.COMPATIBILITY_MODE && main.config.PET.MODULE < gearList.size()) { + moduleId = gearList.get(main.config.PET.MODULE).id; + } + + if (gearOverrideTime > System.currentTimeMillis() && gearOverride != null) { + moduleId = gearOverride; + } + + if (target != null && !(target instanceof Npc) && target.playerInfo.isEnemy()) { + moduleId = PetGearSupplier.Gears.PASSIVE.getId(); + } + + int submoduleId = -1, submoduleIdx = -1; + if (moduleId == PetGearSupplier.Gears.ENEMY_LOCATOR.getId()) { + NpcPick submodule = main.config.LOOT.NPC_INFOS.entrySet() + .stream() + .filter(e -> e.getValue().extra.has(NpcExtra.PET_LOCATOR)) + .sorted(Comparator.comparingInt(e -> e.getValue().priority)) + .map(entry -> new NpcPick(entry.getKey(), entry.getValue())) + .filter(p -> p.gear != null) + .findFirst() + .orElse(null); + if (submodule != null) { + selectedNpc = submodule.npc; + submoduleId = submodule.gear.id; + submoduleIdx = locatorList.indexOf(submodule.gear); + } + } + if (submoduleId == -1) selectedNpc = null; + + if (selection != ModuleStatus.SELECTED + || (currentModule != null && currentModule.id != moduleId) + || (currentSubModule == null && submoduleIdx != -1) + || (currentSubModule != null && currentSubModule.id != submoduleId)) { + if (show(true)) this.selectModule(moduleId, submoduleIdx); + } else if (System.currentTimeMillis() > this.selectModuleTime) show(false); + } + + private class NpcPick { + private final NpcInfo npc; + private final Gear gear; + public NpcPick(String npcName, NpcInfo npc) { + this.npc = npc; + String fuzzyName = npcName.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); + this.gear = locatorList.stream().filter(l -> fuzzyName.equals(l.fuzzyName)).findFirst().orElse(null); + } + } + public NpcInfo getTrackedNpc() { + return selectedNpc; + } + + private void updatePetTarget() { + if (target == null || target.removed || !pet.isAttacking(target)) + target = ships.stream().filter(pet::isAttacking).findFirst().orElse(null); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setOverride(Integer gearId) { + this.gearOverride = gearId; + this.gearOverrideTime = System.currentTimeMillis() + 5000; + } + + private boolean active() { + if (!pet.removed) activeUntil = System.currentTimeMillis() + 1000; + return System.currentTimeMillis() < activeUntil; + } + + private void clickToggleStatus() { + if (System.currentTimeMillis() - this.togglePetTime > 5000L) { + click(MAIN_BUTTON_X, MODULE_Y); + this.selection = ModuleStatus.NOTHING; + this.togglePetTime = System.currentTimeMillis(); + } + } + + private void selectModule(int moduleId, int submoduleIdx) { + if (System.currentTimeMillis() < this.selectModuleTime) return; + this.selectModuleTime = System.currentTimeMillis() + 750; + + switch (selection) { + case SELECTED: + case NOTHING: + click(MODULES_X_MAX - 5, MODULE_Y); + selection = ModuleStatus.DROPDOWN; + break; + case DROPDOWN: + if (submoduleIdx != -1) { + hover(MODULES_X_MAX - 30, getModuleY(moduleId, true)); + selection = ModuleStatus.SUB_DROPDOWN; + } else { + click(MODULES_X_MAX - 30, getModuleY(moduleId, true)); + selection = ModuleStatus.SELECTED; + } + break; + case SUB_DROPDOWN: + selection = ModuleStatus.SELECTED; + if (submoduleIdx != -1) + click(MODULES_X_MAX + 50, getModuleY(moduleId, false) + (SUBMODULE_HEIGHT * submoduleIdx)); + } + + if (selection == ModuleStatus.SELECTED) + this.selectModuleTime = System.currentTimeMillis() + 3000; + } + + private int getModuleY(int moduleId, boolean centered) { + return MODULE_Y + MODULE_Y_OFFSET + (MODULE_HEIGHT * moduleIdToIndex(moduleId)) + + (centered ? (MODULE_HEIGHT / 2) : 0); + } + + private int moduleIdToIndex(int moduleId) { + for (int i = 0; i < gearList.size(); i++) { + if (gearList.get(i).id == moduleId) return i; + } + return 0; + } + + @Override + public void update() { + super.update(); + if (address == 0) return; + + guiSprites.update(address); + long gearsSprite = API.readMemoryLong(guiSprites.getLast() + 216); + gearsArr.update(API.readMemoryLong(API.readMemoryLong(gearsSprite + 176) + 224)); + gearsArr.sync(gearList, Gear::new, null); + + locatorWrapper.update(API.readMemoryLong(gearsSprite + 168)); + + int oldSize = locatorNpcList.getSize(); + locatorNpcList.update(API.readMemoryLong(locatorWrapper.get(0) + 224)); + + // Sometimes the NPC list will be half-updated and there may be way less npcs than before. + // If we have a recent update and list is smaller, we'll ignore updating for a bit + if (locatorNpcList.getSize() < oldSize && validUntil > System.currentTimeMillis()) return; + + validUntil = System.currentTimeMillis() + 100; + locatorNpcList.sync(locatorList, Gear::new, null); + + modulesArr.update(API.readMemoryLong(address + 400)); + + for (int i = 0; i < modulesArr.getSize(); i++) { + if (API.readMemoryInt(modulesArr.get(i) + 172) != 54) continue; + currentArr.update(API.readMemoryLong(modulesArr.get(i) + 184)); + break; + } + for (int i = 0; i < currentArr.getSize(); i++) { + if (API.readMemoryInt(currentArr.get(i) + 168) != 72) continue; + currSpriteWrapper.update(currentArr.get(i)); + break; + } + currSprite.update(API.readMemoryLong(API.readMemoryLong(currSpriteWrapper.get(0) + 216) + 176)); + long currGearCheck = API.readMemoryLong(currSprite.get(1), 216, 152, 0x10); + + currentModule = findGear(gearList, currGearCheck); + if (currentModule != null) currentSubModule = null; + else { + currentSubModule = findGear(locatorList, currGearCheck); + if (currentSubModule != null) currentModule = byId(currentSubModule.parentId); + } + } + + private void updateGear(Gear module, Gear subModule) { + currentModule = module; + currentSubModule = module == null ? null : subModule; + } + + public Gear findGear(List gears, long check) { + for (Gear gear : gears) if (gear.check == check) return gear; + return null; + } + + public Gear byId(int id) { + for (Gear gear : gearList) if (gear.id == id) return gear; + return null; + } + + public static class Gear extends UpdatableAuto { + public int id, parentId; + public long check; + public String name, fuzzyName; + + @Override + public void update() { + this.id = API.readMemoryInt(address + 172); + this.parentId = API.readMemoryInt(address + 176); //assume, -1 if none + this.name = API.readMemoryString(API.readMemoryLong(address + 200)); + this.fuzzyName = name.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); + this.check = API.readMemoryLong(address, 208, 152, 0x10); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/PingManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/PingManager.java index 62fbfd9b..d78d2488 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/manager/PingManager.java +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/PingManager.java @@ -2,79 +2,90 @@ import com.github.manolo8.darkbot.core.BotInstaller; import com.github.manolo8.darkbot.core.itf.Manager; -import com.github.manolo8.darkbot.core.objects.swf.VectorInt; +import com.github.manolo8.darkbot.core.objects.swf.IntArray; + +import java.util.Arrays; import static com.github.manolo8.darkbot.Main.API; public class PingManager implements Manager { - private long pingAddress; - private VectorInt lastPings; - - public int ping; + private volatile IntArray lastPings = null; - private int currentIndex; - private long lastCheck; - private long time; + public int ping = -1; + private int currSize; - public PingManager() { - this.lastPings = new VectorInt(0); - } + private long lastCheck = System.currentTimeMillis() + 60_000; + private long searchTime; + private int retries; @Override public void install(BotInstaller botInstaller) { - botInstaller.invalid.add(value -> pingAddress = 0); + botInstaller.invalid.add(value -> { + reset(); + lastCheck = System.currentTimeMillis() + 60_000; + retries = 0; + }); } - public void tick() { - if (pingAddress == 0) { - - searchPingManager(); - - } else { - - if (System.currentTimeMillis() - lastCheck >= 1000) { - lastPings.update(); - - if (currentIndex == lastPings.size) - ping += 1000; - else if (lastPings.size > 0) - ping = lastPings.elements[lastPings.size - 1]; - - currentIndex = lastPings.size; + public long lastPingUpdate() { + return lastCheck; + } - lastCheck = System.currentTimeMillis(); + public void tick() { + if (searchTime == -1) return; + if (!updatePing()) { + reset(); + if (System.currentTimeMillis() > searchTime) { + searchTime = -1; + searchPingManager(); } } } - private void searchPingManager() { - - if (System.currentTimeMillis() - time > 3000) { - - time = System.currentTimeMillis(); - - long[] result = API.queryMemoryLong(1000, 10_000); - - for (long value : result) { + private void reset() { + ping = -1; + currSize = 0; + lastPings = null; + } - if (API.readMemoryInt(value + 16) != 0) - continue; + private boolean updatePing() { + if (lastPings == null || lastPings.address == 0) return false; + lastPings.update(); + if (lastPings.size < 0 || lastPings.size > 100) return false; - long address = API.readMemoryLong(value + 8); + if (lastPings.size != 0 && currSize != lastPings.size) lastCheck = System.currentTimeMillis(); + else if (System.currentTimeMillis() - lastCheck > 20_000) return false; - if (address == 0) - continue; + this.currSize = lastPings.size; - if (API.readMemoryLong(address) == 0) - continue; + int ping = lastPings.getLast(); + if (ping < -10 || ping > 50_000) return false; - if (API.readMemoryInt(address + 12) != 0 || API.readMemoryInt(address + 36) != 0) - continue; + this.ping = ping; + return true; + } - pingAddress = API.readMemoryLong(address + 40); - lastPings.update(pingAddress); - } - } + private void searchPingManager() { + Arrays.stream(API.queryMemoryInt(15000, 10_000)) + .filter(val -> API.readMemoryInt(val + 16) == 0) + .map(val -> API.readMemoryLong(val + 8)) // Get ping manager address + .distinct() + .filter(addr -> addr != 0) // Check it being ping manager + .filter(addr -> API.readMemoryLong(addr) != 0) + .filter(addr -> API.readMemoryLong(addr + 12) != 0) + .filter(addr -> API.readMemoryLong(addr + 36) != 0) + .map(addr -> API.readMemoryLong(addr + 40)) // Get last pings array + .distinct() + .filter(addr -> addr != 0) // Check array + .filter(addr -> API.readMemoryInt(addr + 64) > 0) // array size > 0 + .mapToObj(IntArray::ofVector) + .peek(IntArray::update) + .filter(vec -> vec.size > 0) + .filter(vec -> Arrays.stream(vec.elements).allMatch(ping -> ping > -1_000 && ping < 50_000)) + .findAny() + .ifPresent(pings -> this.lastPings = pings); + + searchTime = System.currentTimeMillis() + Math.min(300_000, ++retries * 5_000); } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/RepairManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/RepairManager.java new file mode 100644 index 00000000..6f7bfd3e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/RepairManager.java @@ -0,0 +1,109 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.core.BotInstaller; +import com.github.manolo8.darkbot.core.itf.Manager; +import com.github.manolo8.darkbot.core.objects.swf.IntArray; +import com.github.manolo8.darkbot.core.utils.ByteUtils; +import com.github.manolo8.darkbot.utils.LogUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.*; + +import static com.github.manolo8.darkbot.Main.API; + +public class RepairManager implements Manager { + private boolean writtenToLog = true; + private long guiAddress, mainAddress, userDataAddress, repairAddress; + + private String killerName; + private final IntArray repairOptions = IntArray.ofArray(true); + + private final Map streams = new HashMap<>(); + + @Override + public void install(BotInstaller botInstaller) { + botInstaller.guiManagerAddress.add(value -> { + guiAddress = value; + repairAddress = 0; + }); + botInstaller.mainAddress.add(value -> mainAddress = value); + botInstaller.heroInfoAddress.add(value -> userDataAddress = value); + } + + public void tick() { + if (isDead()) writtenToLog = false; + else { + if (!writtenToLog) writeKiller(); + writtenToLog = true; + return; + } + + if (repairAddress == 0) updateRepairAddr(); + + killerName = API.readMemoryString(API.readMemoryLong(repairAddress + 0x68)); + repairOptions.update(API.readMemoryLong(repairAddress + 0x58)); + } + + private void updateRepairAddr() { + long[] values = API.queryMemory(ByteUtils.getBytes(guiAddress, mainAddress), 1); + if (values.length == 1) repairAddress = values[0] - 0x38; + } + + public String getKillerName() { + return killerName; + } + + public boolean isDead() { + return API.readMemoryBoolean(userDataAddress + 0x4C); + } + + public boolean canRespawn(int option) { + for (int i = 0; i < repairOptions.getSize(); i++) { + if (repairOptions.get(i) == option) return true; + } + return false; + } + + public int[] getRespawnOptionsIds() { + int[] options = new int[repairOptions.getSize()]; + for (int i = 0; i < repairOptions.getSize(); i++) { + options[i] = repairOptions.get(i); + } + return options; + } + + private void writeKiller() { + String killerMessage = killerName == null || killerName.isEmpty() + ? "You were destroyed by a radiation/mine/unknown" + : "You have been destroyed by: " + killerName; + System.out.println(killerMessage); + + if (ConfigEntity.INSTANCE.getConfig().MISCELLANEOUS.LOG_DEATHS) + writeToFile(LogUtils.START_TIME + "death", formatLogMessage(killerMessage)); + } + + private String formatLogMessage(String message) { + return String.format("[%s] %-" + message.length() + "s" + System.lineSeparator(), + LocalDateTime.now().format(LogUtils.LOG_DATE), + message); + } + private void writeToFile(String name, String message) { + try { + OutputStream os = getOrCreateStream(name); + if (os == null) return; + + os.write(message.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private OutputStream getOrCreateStream(String name) { + return this.streams.computeIfAbsent(name, LogUtils::createLogFile); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/SettingsManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/SettingsManager.java new file mode 100644 index 00000000..da421b12 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/SettingsManager.java @@ -0,0 +1,42 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.BotInstaller; +import com.github.manolo8.darkbot.core.itf.Manager; +import com.github.manolo8.darkbot.core.itf.Tickable; + +import static com.github.manolo8.darkbot.Main.API; + + +public class SettingsManager implements Manager, Tickable { + + private long address; + + public int config; + public int force2d; + public int nextMap; + public int currMap; + + public String lang; + + public SettingsManager(Main main) {} + + @Override + public void install(BotInstaller botInstaller) { + botInstaller.settingsAddress.add(value -> address = value); + } + + @Override + public void tick() { + this.config = API.readMemoryInt(address + 0x34); + + this.nextMap = API.readMemoryInt(address + 0xCC); + this.currMap = API.readMemoryInt(address + 0xD0); + + this.force2d = API.readMemoryInt(address, 0x2D0, 0x20); + + this.lang = API.readMemoryString(address, 0x258); + } + + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/StarBuilder.java b/src/main/java/com/github/manolo8/darkbot/core/manager/StarBuilder.java new file mode 100644 index 00000000..5df73bcf --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/StarBuilder.java @@ -0,0 +1,173 @@ +package com.github.manolo8.darkbot.core.manager; + +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.objects.Map; +import org.jgrapht.Graph; +import org.jgrapht.graph.DirectedPseudograph; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class StarBuilder { + + private static class TempMap { + private int id; + private String name, shortName; + private boolean isGG; + private List ports = new ArrayList<>(); + + TempMap(int id, String name, boolean isGG) { + this(id, name, name, isGG); + } + TempMap(int id, String name, String shortName, boolean isGG) { + this.id = id; + this.name = name; + this.shortName = shortName; + this.isGG = isGG; + } + } + + private static class TempPort { + private final int x, y, type, factionId; + private final String targetMap; + + TempPort(int x, int y, int type, int factionId, String targetMap) { + this.x = x; + this.y = y; + this.type = type; + this.factionId = factionId; + this.targetMap = targetMap; + } + } + + private class GGPort { + private int type; + private boolean looped; + private String[] maps; + private int x, y; + private String targetMap = current.name; + + public GGPort(int type, boolean looped, String[] maps) { + this.type = type; + this.x = this.y = -1; + this.looped = looped; + this.maps = maps; + } + public GGPort(int type, int x, int y, boolean looped, String[] maps) { + this.type = type; + this.x = x; + this.y = y; + this.looped = looped; + this.maps = maps; + } + } + + private TempMap current; + private List maps = new ArrayList<>(); + private List ggPorts = new ArrayList<>(); + + protected StarBuilder addMap(int id, String name) { + return setCurrent(new TempMap(id, name, false)); + } + + protected StarBuilder addMap(int id, String name, String shortName) { + return setCurrent(new TempMap(id, name, shortName, false)); + } + + protected StarBuilder addGG(int id, String name) { + String shortName = name; + if (name.matches("GG .{0,3} [0-9]+")) { + shortName = name.substring(0, name.lastIndexOf(" ")); + } + return setCurrent(new TempMap(id, name, shortName, true)); + } + + protected StarBuilder addGG(int id, String name, String shortName) { + return setCurrent(new TempMap(id, name, shortName, true)); + } + + private StarBuilder setCurrent(TempMap map) { + this.current = map; + maps.add(map); + return this; + } + + protected StarBuilder addPortal(int x, int y, String targetMap) { + current.ports.add(new TempPort(x, y, -1, -1, targetMap)); + return this; + } + + @SuppressWarnings("SameParameterValue") + protected StarBuilder addPortal(int x, int y, String targetMap, int factionId) { + current.ports.add(new TempPort(x, y, -1, factionId, targetMap)); + return this; + } + + /** + * Adds a type portal to jump to the gate, from one of the maps, and also includes itself (Like most ggs). + */ + protected StarBuilder accessBy(int type, String... maps) { + this.ggPorts.add(new GGPort(type, true, maps)); + return this; + } + + /** + * Adds a type portal to jump to the gate, from one of the maps, and also includes itself (Like most ggs). + */ + protected StarBuilder accessBy(int type, int x, int y, String... maps) { + this.ggPorts.add(new GGPort(type, x, y, true, maps)); + return this; + } + + /** + * Adds a type portal to jump to the gate, from one of the maps, but won't be able to jump from itself (Special cases). + */ + protected StarBuilder accessOnlyBy(int type, String... maps) { + this.ggPorts.add(new GGPort(type, false, maps)); + return this; + } + /** + * Adds a type portal to jump to the gate, from one of the maps, but won't be able to jump from itself (Special cases). + */ + protected StarBuilder accessOnlyBy(int type, int x, int y, String... maps) { + this.ggPorts.add(new GGPort(type, x, y, false, maps)); + return this; + } + + /** + * Adds an exit portal to a galaxy gate map + */ + protected StarBuilder exitBy(int type) { + current.ports.add(new TempPort(-1, -1, type, -1, "Home Map")); + return this; + } + + public Graph build() { + DirectedPseudograph graph = new DirectedPseudograph<>(Portal.class); + HashMap mapsByName = new HashMap<>(); + for (TempMap tmpMap : maps) { + Map map = new Map(tmpMap.id, tmpMap.name, tmpMap.shortName, tmpMap.name.startsWith("4-"), tmpMap.isGG); + mapsByName.put(map.name, map); + graph.addVertex(map); + } + + for (TempMap map : maps) { + for (TempPort port : map.ports) { + Map target = mapsByName.get(port.targetMap); + graph.addEdge(mapsByName.get(map.name), target, new Portal(port.type, port.x, port.y, target, port.factionId)); + } + } + + for (GGPort ggPort : ggPorts) { + Map gg = mapsByName.get(ggPort.targetMap); + if (ggPort.looped) graph.addEdge(gg, gg, new Portal(ggPort.type, -1, -1, gg, -1)); + for (String mapName : ggPort.maps) { + Map from = mapsByName.get(mapName); + graph.addEdge(from, gg, new Portal(ggPort.type, ggPort.x, ggPort.y, gg, -1)); + } + } + return graph; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/StarManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/StarManager.java index a6c58f04..d3bce13f 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/manager/StarManager.java +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/StarManager.java @@ -1,338 +1,257 @@ package com.github.manolo8.darkbot.core.manager; +import com.github.manolo8.darkbot.config.types.suppliers.OptionList; import com.github.manolo8.darkbot.core.entities.Portal; -import com.github.manolo8.darkbot.core.objects.LocationInfo; import com.github.manolo8.darkbot.core.objects.Map; +import com.github.manolo8.darkbot.utils.I18n; +import org.jgrapht.Graph; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import java.io.StringWriter; +import java.io.Writer; import java.util.Collection; -import java.util.HashMap; -import java.util.TreeMap; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class StarManager { + public static final String[] HOME_MAPS = new String[]{"1-1", "2-1", "3-1"}; + public static final String[] OUTPOST_HOME_MAPS = new String[]{"1-8", "2-8", "3-8"}; - private HashMap starSystem; - private TreeMap starSystemName; - private HashMap portals; + private static StarManager INSTANCE; + private final Graph starSystem; - public StarManager() { - - starSystem = new HashMap<>(); - starSystemName = new TreeMap<>(); - portals = new HashMap<>(); - - //MMO -------------------------------------------------------- - - //1-1 - add(1, "1-1", new Portal(150000159, 2)); - - //1-2 - add(2, "1-2", - new Portal(150000160, 1), - new Portal(150000161, 3), - new Portal(150000163, 4) - ); - - //1-3 - add(3, "1-3", - new Portal(150000162, 2), - new Portal(150000185, 4), - new Portal(150000165, 7) - ); - - //1-4 - add(4, "1-4", - new Portal(150000164, 2), - new Portal(150000186, 3), - new Portal(150000189, 13), - new Portal(150000169, 12) - ); - - //1-5 - add(17, "1-5", - new Portal(150000299, 16), - new Portal(150000328, 29), - new Portal(150000300, 18), - new Portal(150000302, 19) - ); - - //1-6 - add(18, "1-6", - new Portal(150000301, 17), - new Portal(150000304, 20) - ); - - //1-7 - add(19, "1-7", - new Portal(150000306, 20), - new Portal(150000303, 17) - ); - - //1-8 - add(20, "1-8", - new Portal(150000305, 18), - new Portal(150000307, 19) - ); - - //MMO -------------------------------------------------------- - - //EIC -------------------------------------------------------- - - //2-1 - add(5, "2-1", new Portal(150000174, 6)); - - //2-2 - add(6, "2-2", - new Portal(150000168, 7), - new Portal(150000175, 8), - new Portal(150000173, 5) - ); - - //2-3 - add(7, "2-3", - new Portal(150000166, 3), - new Portal(150000183, 8), - new Portal(150000167, 6) - ); - - //2-4 - add(8, "2-4", - new Portal(150000184, 7), - new Portal(150000191, 14), - new Portal(150000176, 6), - new Portal(150000177, 11) - ); - - //2-5 - add(21, "2-5", - new Portal(150000330, 16), - new Portal(150000309, 29), - new Portal(150000310, 22), - new Portal(150000312, 23) - ); - - //2-6 - add(22, "2-6", - new Portal(150000311, 21), - new Portal(150000314, 24) - ); - - //2-7 - add(23, "2-7", - new Portal(150000313, 21), - new Portal(150000316, 24) - ); - - //2-8 - add(24, "2-8", - new Portal(150000315, 22), - new Portal(150000317, 23) - ); - - //EIC -------------------------------------------------------- - - //VRU -------------------------------------------------------- - - //3-1 - add(9, "3-1", new Portal(150000182, 10)); - - //3-2 - add(10, "3-2", - new Portal(150000180, 11), - new Portal(150000172, 12), - new Portal(150000181, 9) - ); - - //3-3 - add(11, "3-3", - new Portal(150000178, 8), - new Portal(150000188, 12), - new Portal(150000179, 10) - ); - - //3-4 - add(12, "3-4", - new Portal(150000170, 4), - new Portal(150000193, 15), - new Portal(150000187, 11), - new Portal(150000171, 10) - ); - - //3-5 - add(25, "3-5", - new Portal(150000319, 16), - new Portal(150000332, 29), - new Portal(150000320, 26), - new Portal(150000322, 27) - ); - - //3-6 - add(26, "3-6", - new Portal(150000321, 25), - new Portal(150000324, 28) - ); - - //3-7 - add(27, "3-7", - new Portal(150000323, 25), - new Portal(150000326, 28) - ); - - //3-8 - add(28, "3-8", - new Portal(150000327, 27), - new Portal(150000325, 26), - new Portal(150000209, 308) - ); - //VRU -------------------------------------------------------- - - //PVP -------------------------------------------------------- - - //4-1 - add(13, "4-1", - new Portal(150000190, 4), - new Portal(150000195, 14), - new Portal(150000200, 15), - new Portal(150000289, 16) - ); + private static int INVALID_MAP_ID = -999; - //4-2 - add(14, "4-2", - new Portal(150000192, 8), - new Portal(150000196, 13), - new Portal(150000197, 15), - new Portal(150000291, 16) - ); - - //4-3 - add(15, "4-3", - new Portal(150000194, 12), - new Portal(150000198, 14), - new Portal(150000199, 13), - new Portal(150000293, 16) - ); - - //4-4 - add(16, "4-4", - new Portal(150000318, 25), - new Portal(150000294, 15), - new Portal(150000292, 14), - new Portal(150000308, 21), - new Portal(150000298, 17), - new Portal(150000290, 13) - ); - - //4-5 - add(29, "4-5", - new Portal(150000329, 17), - new Portal(150000331, 21), - new Portal(150000333, 15), - new Portal(150000428, 91), - new Portal(150000432, 91), - new Portal(150000430, 91) - ); - //PVP -------------------------------------------------------- - - //PIRATES - add(91, "5-1", - new Portal(150000434, 92), - new Portal(150000436, 92), - new Portal(150000438, 92) - ); - - add(92, "5-2", - new Portal(150000440, 93), - new Portal(150000442, 93), - new Portal(150000444, 93) - ); - - add(93, "5-3", - new Portal(150000446, 16), - new Portal(150000448, 16), - new Portal(150000450, 16) - ); - //PIRATES - - //NEW MAPS - - add(308, "*3B-L", - new Portal(150000210, 28) - ); - - - //NEW MAPS - - fill(); + public StarManager() { + INSTANCE = this; + + StarBuilder mapBuild = new StarBuilder(); + mapBuild.addMap(-1, I18n.get("gui.map.loading"), "?") + .addMap(-2, "Home Map").addPortal(0, 0, "1-1").addPortal(0, 0, "2-1").addPortal(0, 0, "3-1"); + //.addGG(-3, "GG Escort").accessOnlyBy(54, 10500, 6500, /*"1-1", "2-1", "3-1",*/ OUTPOST_HOME_MAPS) // Gotta "reserve" x-1 maps for GG eternal. + //.addGG(-4, "GG Eternal").accessBy(54, 10500, 6500, HOME_MAPS); + // MMO + mapBuild.addMap(1, "1-1").addPortal(18500, 11500, "1-2").addPortal(10500, 6750, "Experiment Zone 1") + .addMap(2, "1-2").addPortal(2000, 2000, "1-1").addPortal(18500, 2000, "1-3").addPortal(18500, 11500, "1-4") + .addMap(3, "1-3").addPortal(2000, 11500, "1-2").addPortal(18500, 11500, "1-4").addPortal(18500, 2000, "2-3") + .addMap(4, "1-4").addPortal(2000, 2000, "1-2").addPortal(18500, 2000, "1-3").addPortal(19000, 6000, "4-1").addPortal(18500, 11500, "3-4") + .addMap(17, "1-5").addPortal(19000, 6000, "4-4").addPortal(10000, 12000, "4-5").addPortal(2000, 2000, "1-6").addPortal(2000, 11500, "1-7").addPortal(10500, 6750, "Experiment Zone 2-1") + .addMap(18, "1-6").addPortal(18500, 11500, "1-5").addPortal(2000, 11500, "1-8") + .addMap(19, "1-7").addPortal(2000, 2000, "1-8").addPortal(18500, 2000, "1-5") + .addMap(20, "1-8").addPortal(18500, 2000, "1-6").addPortal(18500, 11500, "1-7").addPortal(11084, 11084, "1BL"); + // EIC + mapBuild.addMap(5, "2-1").addPortal(2000, 11500, "2-2").addPortal(10500, 6750, "Experiment Zone 1") + .addMap(6, "2-2").addPortal(2000, 11500, "2-3").addPortal(18500, 11500, "2-4").addPortal(18500, 2000, "2-1") + .addMap(7, "2-3").addPortal(2000, 11500, "1-3").addPortal(18500, 11500, "2-4").addPortal(18500, 2000, "2-2") + .addMap(8, "2-4").addPortal(2000, 2000, "2-2").addPortal(18500, 2000, "2-3").addPortal(2000, 11500, "3-3").addPortal(10000, 12000, "4-2") + .addMap(21, "2-5").addPortal(2000, 11500, "4-4").addPortal(18500, 11500, "4-5").addPortal(2000, 2000, "2-6").addPortal(18500, 2000, "2-7").addPortal(10500, 6750, "Experiment Zone 2-2") + .addMap(22, "2-6").addPortal(2000, 11500, "2-5").addPortal(18500, 2000, "2-8") + .addMap(23, "2-7").addPortal(2000, 11500, "2-5").addPortal(18500, 2000, "2-8") + .addMap(24, "2-8").addPortal(2000, 11500, "2-6").addPortal(18500, 11500, "2-7").addPortal(11084, 11084, "2BL"); + // VRU + mapBuild.addMap(9, "3-1").addPortal(2000, 2000, "3-2").addPortal(10500, 6750, "Experiment Zone 1") + .addMap(10, "3-2").addPortal(18500, 2000, "3-3").addPortal(2000, 2000, "3-4").addPortal(18500, 11500, "3-1") + .addMap(11, "3-3").addPortal(2000, 2000, "2-4").addPortal(2000, 11500, "3-4").addPortal(18500, 11500, "3-2") + .addMap(12, "3-4").addPortal(2000, 2000, "1-4").addPortal(10000, 1500, "4-3").addPortal(18500, 2000, "3-3").addPortal(18500, 11500, "3-2") + .addMap(25, "3-5").addPortal(2000, 2000, "4-4").addPortal(16500, 1500, "4-5").addPortal(2000, 11500, "3-6").addPortal(18500, 11500, "3-7").addPortal(10500, 6750, "Experiment Zone 2-3") + .addMap(26, "3-6").addPortal(2000, 2000, "3-5").addPortal(18500, 11500, "3-8") + .addMap(27, "3-7").addPortal(2000, 11500, "3-5").addPortal(18500, 11500, "3-8") + .addMap(28, "3-8").addPortal(2000, 2000, "3-7").addPortal(2000, 11500, "3-6").addPortal(11084, 11084, "3BL"); + // B-MAPS + mapBuild.addMap(13, "4-1").addPortal(1500, 6000, "1-4").addPortal(18500, 2000, "4-2").addPortal(18500, 11500, "4-3").addPortal(10500, 6750, "4-4") + .addMap(14, "4-2").addPortal(10000, 1500, "2-4").addPortal(2000, 11500, "4-1").addPortal(18500, 11500, "4-3").addPortal(10500, 6750, "4-4") + .addMap(15, "4-3").addPortal(19000, 6000, "3-4").addPortal(2000, 2000, "4-2").addPortal(2000, 11500, "4-1").addPortal(10500, 6750, "4-4") + .addMap(16, "4-4").addPortal(7000, 13500, "1-5").addPortal(28000, 1376, "2-5").addPortal(28000, 25124, "3-5").addPortal(19200, 13500, "4-1").addPortal(21900, 11941, "4-2").addPortal(21900, 14559, "4-3") + .addMap(29, "4-5").addPortal(7000, 13500, "1-5").addPortal(28000, 1376, "2-5").addPortal(28000, 25624, "3-5").addPortal(12200, 13300, "5-1").addPortal(25000, 6300, "5-1").addPortal(25000, 20700, "5-1"); + // Pirates + mapBuild.addMap(91, "5-1").addPortal(5200, 6800, "5-2").addPortal(2900, 13500, "5-2").addPortal(5200, 20600, "5-2") + .addMap(92, "5-2").addPortal(2800, 3600, "5-3").addPortal(1300, 6750, "5-3").addPortal(2800, 10900, "5-3") + .addMap(93, "5-3").addPortal(2000, 9500, "4-4", 1).addPortal(2000, 13500, "4-4", 2).addPortal(2000, 17500, "4-4", 3); + // BL + mapBuild.addMap(306, "1BL").addPortal( 786, 11458, "1-8").addPortal( 7589, 1456, "2BL").addPortal(20072, 11732, "3BL") + .addMap(307, "2BL").addPortal(9893, 862, "1BL").addPortal( 593, 5884, "2-8").addPortal(20377, 7996, "3BL") + .addMap(308, "3BL").addPortal(1545, 12210, "1BL").addPortal(19400, 11854, "2BL").addPortal(14027, 3181, "3-8"); + // EX + mapBuild.addMap(401, "Experiment Zone 1", "EZ1").addPortal(1200, 1100, "Home Map") + .addMap(402, "Experiment Zone 2-1", "EZ 2-1").addPortal(1200, 1100, "1-5") + .addMap(403, "Experiment Zone 2-2", "EZ 2-2").addPortal(1200, 1100, "2-5") + .addMap(404, "Experiment Zone 2-3", "EZ 2-3").addPortal(1200, 1100, "3-5"); + // GG + mapBuild.addGG(51, "GG α").accessBy(2, HOME_MAPS) + .addGG(52, "GG β").accessBy(3, HOME_MAPS) + .addGG(53, "GG γ").accessBy(4, HOME_MAPS) + .addGG(54, "GG NC") // New Client GG (No access) + .addGG(55, "GG δ").accessBy(5, HOME_MAPS) + .addGG(56, "GG Orb")// The forgotten gate (No access) + .addGG(57, "GG Y6").accessBy(16, HOME_MAPS) // Year 6 (Anniversary) + .addGG(57, "HSG") // High Score Gate (No access) + .addGG(70, "GG ε").accessBy(53, HOME_MAPS) + .addGG(71, "GG ζ 1").accessOnlyBy(54, HOME_MAPS) + .addGG(72, "GG ζ 2").accessOnlyBy(54, "GG ζ 1") + .addGG(73, "GG ζ 3").accessBy(54, "GG ζ 2") + .addGG(74, "GG κ").accessBy(70, HOME_MAPS) + .addGG(75, "GG λ").accessBy(71, HOME_MAPS) + .addGG(76, "GG Kronos", "GG K").accessBy(72, HOME_MAPS) + .addGG(77, "GG Cold Wave (Easy)", "GG Cold").accessBy(73, "1-4", "2-4", "3-4") + .addGG(78, "GG Cold Wave (Hard)", "GG Cold").accessBy(73, "1-4", "2-4", "3-4") + .addGG(203, "GG Hades", "Hades").accessBy(74, HOME_MAPS) + .addGG(223, "Devolarium Attack", "DA") // (No access), missing type ID (HOME_MAPS) + .addGG(225, "GG PET Attack (easy)") // (No access) + .addGG(226, "GG PET Attack (hard)") // (No access) + .addGG(228, "Permafrost Fissure") // (No access), missing type ID (HOME_MAPS) + .addGG(300, "GG ς 1").accessOnlyBy(82, HOME_MAPS) + .addGG(301, "GG ς 2").accessOnlyBy(82, "GG ς 1") + .addGG(302, "GG ς 3").accessOnlyBy(82, "GG ς 2") + .addGG(303, "GG ς 4").accessOnlyBy(82, "GG ς 3") + .addGG(304, "GG ς 5").accessBy(82, "GG ς 4") + .addGG(200, "LoW").accessOnlyBy(34, "1-3", "2-3", "3-3") + .addGG(229, "Quarantine Zone", "QZ").accessOnlyBy(84, "1-7", "2-7", "3-7") + .addGG(227, "GG VoT 1").accessOnlyBy(81, "1-4", "2-4", "3-4") + .addGG(230, "GG VoT 2").accessOnlyBy(81, "GG VoT 1") + .addGG(231, "GG VoT 3").accessOnlyBy(81, "GG VoT 2") + .addGG(232, "GG VoT 4").accessOnlyBy(81, "GG VoT 3") + .addGG(233, "GG VoT 5").accessOnlyBy(81, "GG VoT 4") + .addGG(234, "GG VoT 6").accessOnlyBy(81, "GG VoT 5") + .addGG(235, "GG VoT 7").accessOnlyBy(81, "GG VoT 6") + .addGG(236, "GG VoT 8").accessBy(81, "GG VoT 7") + .addGG(305, "Compromising Invasion") // (No access) + .addGG(410, "GoP Normal 1").accessOnlyBy(24, "1-8", "2-8", "3-8") + .addGG(411, "GoP Normal 2").accessOnlyBy(24, "GoP Normal 1") + .addGG(412, "GoP Normal 3").accessOnlyBy(24, "GoP Normal 2") + .addGG(413, "GoP Normal 4").accessOnlyBy(24, "GoP Normal 3") + .addGG(414, "GoP Normal 5").accessOnlyBy(24, "GoP Normal 4") + .addGG(415, "GoP Normal Final").accessOnlyBy(24, "GoP Normal 5").exitBy(1) + .addGG(450, "GoP Easy 1").accessOnlyBy(235, HOME_MAPS) + .addGG(451, "GoP Easy 2").accessOnlyBy(235, "GoP Easy 1") + .addGG(452, "GoP Easy 3").accessOnlyBy(235, "GoP Easy 2") + .addGG(453, "GoP Easy 4").accessOnlyBy(235, "GoP Easy 3") + .addGG(454, "GoP Easy 5").accessOnlyBy(235, "GoP Easy 4") + .addGG(455, "GoP Easy Final").accessOnlyBy(235, "GoP Easy 5").exitBy(1); + // Special (No direct access) + mapBuild.addMap(42, "???") + .addMap(61, "MMO Invasion").addMap(62, "EIC Invasion").addMap(63, "VRU Invasion") + .addMap(64, "MMO Invasion").addMap(65, "EIC Invasion").addMap(66, "VRU Invasion") + .addMap(67, "MMO Invasion").addMap(68, "EIC Invasion").addMap(69, "VRU Invasion") + .addMap(81, "TDM I").addMap(82, "TDM II") + .addMap(101, "JP").addMap(101, "JP").addMap(102, "JP").addMap(103, "JP") + .addMap(104, "JP").addMap(105, "JP").addMap(106, "JP").addMap(107, "JP") + .addMap(108, "JP").addMap(109, "JP").addMap(110, "JP").addMap(111, "JP") + .addMap(112, "UBA").addMap(113, "UBA").addMap(114, "UBA").addMap(115, "UBA").addMap(116, "UBA") + .addMap(117, "UBA").addMap(118, "UBA").addMap(119, "UBA").addMap(120, "UBA").addMap(121, "UBA") + .addMap(201, "SC-1").addMap(201, "SC-2") // Sector Control + .addMap(224, "Custom Tournament") + .addMap(150, "R-Zone 1").addMap(151, "R-Zone 2") + .addMap(152, "R-Zone 3").addMap(153, "R-Zone 4") + .addMap(154, "R-Zone 5").addMap(155, "R-Zone 6") + .addMap(156, "R-Zone 7").addMap(157, "R-Zone 8") + .addMap(158, "R-Zone 9").addMap(159, "R-Zone 10") + .addMap(420, "WarGame 1").addMap(421, "WarGame 2").addMap(422, "WarGame 3") + .addMap(423, "WarGame 4").addMap(423, "WarGame 5").addMap(423, "WarGame 6"); + // Mimesis escort + mapBuild.addGG(430, "Escort VRU 1", "ESC-V1").exitBy(1) + .addGG(431, "Escort VRU 2", "ESC-V2").exitBy(1) + .addGG(432, "Escort VRU 3", "ESC-V3").exitBy(1) + .addGG(433, "Escort MMO 1", "ESC-M1").exitBy(1) + .addGG(434, "Escort MMO 2", "ESC-M2").exitBy(1) + .addGG(435, "Escort MMO 3", "ESC-M3").exitBy(1) + .addGG(436, "Escort EIC 1", "ESC-E1").exitBy(1) + .addGG(437, "Escort EIC 2", "ESC-E2").exitBy(1) + .addGG(438, "Escort EIC 3", "ESC-E3").exitBy(1) + .addGG(439, "Eternal Gate", "GG ∞") + .addGG(440, "Eternal Gate", "GG ∞") + .addGG(441, "Eternal Gate", "GG ∞") + .addGG(442, "Eternal Gate", "GG ∞") + .addGG(443, "Eternal Gate", "GG ∞") + .addGG(444, "Eternal Gate", "GG ∞") + .addGG(445, "Eternal Gate", "GG ∞"); + + starSystem = mapBuild.build(); + + //org.jgrapht.io.DOTExporter exporter = new org.jgrapht.io.DOTExporter<>(m -> (m.id < 0 ? "00" : "") + Math.abs(m.id), Map::toString, Portal::toString); + //Writer writer = new StringWriter(); + //exporter.exportGraph(starSystem, writer); + //System.out.println(writer.toString()); } - public Portal fromIdPortal(int id) { - Portal portal = portals.get(id); - - if (portal == null) { - portal = new Portal(id, 0); - } - - return portal; + public Portal getOrCreate(int id, int type, int x, int y) { + return starSystem.outgoingEdgesOf(HeroManager.instance.map).stream() + .filter(p -> p.matches(x, y, type)) + .peek(p -> p.id = id) + .findFirst() + .orElse(new Portal(id, type, x, y)); } - public Portal next(Map current, LocationInfo locationInfo, Map target) { - - Portal closest = null; - - double distance = 1000000; - int min = -1; - - for (Portal portal : current.portals) { - int dist = target.distances.get(portal.target); - - if (min == -1 || dist <= min) { + public Portal next(HeroManager hero, Map target) { + DijkstraShortestPath path = new DijkstraShortestPath<>(starSystem); + return starSystem.outgoingEdgesOf(hero.map).stream().filter(p -> !p.removed).min( + Comparator.comparingDouble(p -> path.getPaths(p.target).getWeight(target)) + .thenComparing(p -> p.factionId == -1 ? 0 : p.factionId == hero.playerInfo.factionId ? -1 : 1) + .thenComparing(p -> hero.locationInfo.distance(p.locationInfo))).orElse(null); + } - if (dist != min || distance > portal.locationInfo.distance(locationInfo)) { - min = dist; - closest = portal; - distance = portal.locationInfo.distance(locationInfo); - } + public Map byName(String name) { + return starSystem.vertexSet().stream().filter(m -> m.name.equals(name)).findAny() + .orElseGet(() -> addMap(new Map(--INVALID_MAP_ID, name, false, false))); + } - } + public Stream mapSet() { + return starSystem.vertexSet().stream(); + } - } + public Map byId(int id) { + return starSystem.vertexSet().stream().filter(m -> m.id == id).findAny() + .orElseGet(() -> addMap(new Map(id, "Unknown map " + id, false, false))); + } - return closest; + private Map addMap(Map map) { + starSystem.addVertex(map); + return map; } - public Map fromName(String name) { - return starSystemName.get(name); + public List getAccessibleMaps() { + return starSystem.vertexSet().stream() + .filter(m -> !m.gg && m.id > 0) + .filter(m -> starSystem.inDegreeOf(m) > 0) + .map(m -> m.name).sorted().collect(Collectors.toList()); } - public Map fromId(int id) { - Map map = starSystem.get(id); + public Collection getGGMaps() { + return starSystem.vertexSet().stream() + .filter(m -> m.gg) + .filter(m -> starSystem.inDegreeOf(m) > 0 && + (starSystem.outDegreeOf(m) == 0 || starSystem.containsEdge(m, m))) + .map(m -> m.name) + .sorted().collect(Collectors.toList()); + } - if (map == null) { - map = new Map(id, "Unknown map " + id, false, new Portal[0]); - } + public static Collection getAllMaps() { + return INSTANCE.starSystem.vertexSet(); + } - return map; + public static StarManager getInstance() { + return INSTANCE; } - private void add(int id, String name, Portal... portals) { - Map map = new Map(id, name, name.startsWith("4"), portals); - this.starSystem.put(id, map); - this.starSystemName.put(map.name, map); - for (Portal portal : portals) { - this.portals.put(portal.id, portal); + public static class MapList extends OptionList { + + @Override + public Integer getValue(String text) { + return INSTANCE.byName(text).id; } - } - private void fill() { - for (Portal portal : portals.values()) { - portal.target = starSystem.get(portal.targetId); + @Override + public String getText(Integer value) { + return INSTANCE.byId(value).name; } - for (Map map : starSystem.values()) { - map.recursiveDistance(); + @Override + public List getOptions() { + return INSTANCE.getAccessibleMaps(); } } - public Collection getAllMaps() { - return starSystemName.keySet(); - } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/manager/StatsManager.java b/src/main/java/com/github/manolo8/darkbot/core/manager/StatsManager.java index 9ed72daf..20d3e4b0 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/manager/StatsManager.java +++ b/src/main/java/com/github/manolo8/darkbot/core/manager/StatsManager.java @@ -3,12 +3,19 @@ import com.github.manolo8.darkbot.Main; import com.github.manolo8.darkbot.core.BotInstaller; import com.github.manolo8.darkbot.core.itf.Manager; +import com.github.manolo8.darkbot.modules.DisconnectModule; +import com.github.manolo8.darkbot.utils.I18n; import static com.github.manolo8.darkbot.Main.API; public class StatsManager implements Manager { + private Main main; + private long address; + private long settingsAddress; + + public long currentBox; // Pretty out of place, but will work public double credits; public double uridium; @@ -17,8 +24,8 @@ public class StatsManager implements Manager { public int deposit; public int depositTotal; - private long started; - private long runningTime; + private long started = System.currentTimeMillis(); + private long runningTime = 1; private boolean lastStatus; public double earnedCredits; @@ -26,37 +33,49 @@ public class StatsManager implements Manager { public double earnedExperience; public double earnedHonor; - public String sid; - - private StringBuilder builder; + public volatile String sid; + public volatile String instance; public StatsManager(Main main) { - builder = new StringBuilder(); - - main.status.add(this::toggle); + this.main = main; + this.main.status.add(this::toggle); } @Override public void install(BotInstaller botInstaller) { - botInstaller.userDataAddress.add(value -> { + botInstaller.heroInfoAddress.add(value -> { address = value; sid = API.readMemoryString(API.readMemoryLong(address + 168)); }); + botInstaller.settingsAddress.add(value -> { + settingsAddress = value; + instance = null; + }); } public void tick() { - if (address != 0) { - updateCredits(API.readMemoryDouble(address + 288)); - updateUridium(API.readMemoryDouble(address + 296)); - updateExperience(API.readMemoryDouble(address + 312)); - updateHonor(API.readMemoryDouble(address + 320)); - - deposit = API.readMemoryInt(API.readMemoryLong(address + 240) + 40); - depositTotal = API.readMemoryInt(API.readMemoryLong(address + 248) + 40); + if (address == 0) return; + updateCredits(API.readMemoryDouble(address + 288)); + updateUridium(API.readMemoryDouble(address + 296)); + //API.readMemoryDouble(address + 304); // Jackpot + updateExperience(API.readMemoryDouble(address + 312)); + updateHonor(API.readMemoryDouble(address + 320)); + + deposit = API.readMemoryInt(API.readMemoryLong(address + 240) + 40); + depositTotal = API.readMemoryInt(API.readMemoryLong(address + 248) + 40); + + currentBox = API.readMemoryLong(address + 0xE8); + + if (settingsAddress == 0) return; + if (instance == null || instance.isEmpty() || !instance.startsWith("http")) { + instance = API.readMemoryString(API.readMemoryLong(settingsAddress + 616)); } } + public int getLevel() { + return Math.max(1, (int) (Math.log(experience / 10_000) / Math.log(2)) + 2); + } public void toggle(boolean running) { lastStatus = running; @@ -89,51 +108,30 @@ private void updateUridium(double uridium) { } private void updateExperience(double experience) { + if (experience == 0) return; if (this.experience != 0) earnedExperience += experience - this.experience; this.experience = experience; } private void updateHonor(double honor) { - if (this.honor != 0) earnedHonor += honor - this.honor; + if (honor == 0) return; + double honorDiff = honor - this.honor; + if (this.honor != 0) earnedHonor += honorDiff; this.honor = honor; - } - - public long runningTime() { - return runningTime + (lastStatus ? (System.currentTimeMillis() - started) : 0); - } - public String runningTimeStr() { - builder.setLength(0); + if (honorDiff > -10_000) return; - int seconds = (int) (runningTime() / 1000); - int hours = seconds / 3600; - int minutes = seconds / 60; + System.out.println("Paused bot, lost " + honorDiff + " honor."); + double friendlies = Math.log(Math.abs(honorDiff) / 100) / Math.log(2); + boolean isExact = Math.abs(friendlies - Math.round(friendlies)) < 0.01; + System.out.println("Look like " + friendlies + " friendly kills, credible & pausing: " + isExact); + if (!main.config.MISCELLANEOUS.HONOR_LOST_EXACT || isExact) + main.setModule(new DisconnectModule(null, I18n.get("module.disconnect.reason.honor"))); + } - if (hours > 0) { - - if (hours < 10) - builder.append('0'); - - builder.append(hours).append(':'); - } - - if (minutes > 0) { - - if (minutes < 10) - builder.append('0'); - - builder.append(minutes).append(':'); - } - - seconds = seconds % 60; - - if (seconds < 10) - builder.append('0'); - - builder.append(seconds); - - return builder.toString(); + public long runningTime() { + return runningTime + (lastStatus ? (System.currentTimeMillis() - started) : 0); } public double earnedCredits() { diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/Clickable.java b/src/main/java/com/github/manolo8/darkbot/core/objects/Clickable.java index c7311de2..b31523df 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/Clickable.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/Clickable.java @@ -11,57 +11,58 @@ public class Clickable extends Updatable { public int radius; public int priority; - private int defRadius; - private int defPriority; + public int defRadius = -1; + public int defPriority = -1; public void setPriority(int priority) { - - update(); - - if (this.priority != priority && checkIntegrity()) { - if (defPriority == 0) defPriority = this.priority; - API.writeMemoryInt(address + 44, this.priority = priority); - } + if (this.priority == priority || isInvalid()) return; + if (defPriority == -1) this.defPriority = this.priority; + API.writeMemoryInt(address + 44, this.priority = priority); } public void setRadius(int radius) { - - update(); - - if (this.radius != radius && checkIntegrity()) { - if (defRadius == 0) defRadius = this.radius; - API.writeMemoryInt(address + 40, radius); - } + if (this.radius == radius || isInvalid()) return; + if (defRadius == -1) this.defRadius = this.radius; + if (defRadius <= 0) return; + API.writeMemoryInt(address + 40, this.radius = radius); } public void reset() { - - update(); - - if (checkIntegrity()) { - if (defRadius != 0 && defRadius != radius) API.writeMemoryInt(address + 40, defRadius); - if (defPriority != 0 && defPriority != priority) API.writeMemoryInt(address + 44, defPriority); - defRadius = 0; - defPriority = 0; - } + if (isInvalid()) return; + if (defRadius != -1 && defRadius != radius) API.writeMemoryInt(address + 40, radius = defRadius); + if (defRadius != -1 && defPriority != priority) API.writeMemoryInt(address + 44, priority = defPriority); } /** * @return prevent swf crash */ - private boolean checkIntegrity() { - return API.readMemoryLong(address) == confirm; + private boolean isInvalid() { + return defRadius <= 0 || address == 0 || API.readMemoryLong(address) != confirm; } @Override public void update() { + if (isInvalid()) return; + int oldRad = radius, oldPri = priority; this.radius = API.readMemoryInt(address + 40); this.priority = API.readMemoryInt(address + 44); + + if (radius != oldRad) { + if (oldRad != defRadius) defRadius = radius; + setRadius(oldRad); + } + if (priority != oldPri) { + if (oldPri != defPriority) defPriority = priority; + setPriority(oldPri); + } } @Override public void update(long address) { super.update(address); + if (address == 0) return; this.confirm = API.readMemoryLong(address); + this.radius = defRadius = API.readMemoryInt(address + 40); + this.priority = defPriority = API.readMemoryInt(address + 44); } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/Gui.java b/src/main/java/com/github/manolo8/darkbot/core/objects/Gui.java index 57dd724f..92795ed8 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/Gui.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/Gui.java @@ -10,59 +10,51 @@ public class Gui extends Updatable { public long addressInfo; public boolean visible; + protected boolean isTweening; // If it's in the middle of an animation - private Point minimized; - private LocationInfo size; - private LocationInfo pos; + protected final Point pos = new Point(); + protected final Point size = new Point(); + protected final Point minimized = new Point(); public int x; public int y; public int width; public int height; - private long time; - private long update; - - public Gui(long address) { - - this.address = address; - - this.size = new LocationInfo(0); - this.pos = new LocationInfo(0); - this.minimized = new Point(0); - - update(); - } + protected long time; + protected long update; public void update() { - -// if (address != 0) { -// size.update(API.readMemoryLong(addressInfo + 10 * 8)); -// pos.update(API.readMemoryLong(addressInfo + 9 * 8)); -// minimized.update(API.readMemoryLong(addressInfo + 14 * 8)); -// -// size.update(); -// pos.update(); -// minimized.update(); -// -// width = (int) Math.round(size.x); -// height = (int) Math.round(size.y); -// x = (int) Math.round((MapManager.clientWidth - size.x) * 0.01 * pos.x); -// y = (int) Math.round((MapManager.clientHeight - size.y) * 0.01 * pos.y); -// -// visible = API.readMemoryBoolean(addressInfo + 32); -// } - + if (address == 0) return; + pos.update(API.readMemoryLong(addressInfo + 9 * 8)); + size.update(API.readMemoryLong(addressInfo + 10 * 8)); + // 11 * 8 = FeatureDefinitionVo + // 12 * 8 = help text + // 13 * 8 = tool tip + minimized.update(API.readMemoryLong(addressInfo + 14 * 8)); + + width = (int) Math.round(size.x); + height = (int) Math.round(size.y); + // Set pos relative to window size + x = (int) Math.round((MapManager.clientWidth - size.x) * 0.01 * pos.x); + y = (int) Math.round((MapManager.clientHeight - size.y) * 0.01 * pos.y); + + visible = API.readMemoryBoolean(addressInfo + 32); // Maximized + // API.readMemoryBoolean(addressInfo + 36); // Toggle maximize (set to true/false when pressing H to show/hide) + // API.readMemoryBoolean(addressInfo + 40); // Maximized changed (set to true when toggling maximized) + // API.readMemoryBoolean(addressInfo + 44); // Settings on server + // API.readMemoryBoolean(addressInfo + 48); // show on top + + isTweening = API.readMemoryBoolean(address + 0xC4); } @Override public void update(long address) { - if (address == 0) { reset(); } else { super.update(address); - this.addressInfo = API.readMemoryLong(address + 488); + this.addressInfo = API.readMemoryLong(address + 496); this.update = System.currentTimeMillis(); } } @@ -72,28 +64,43 @@ public void reset() { this.visible = false; this.height = 0; this.width = 0; + this.update = 0; } public boolean lastUpdatedIn(long time) { - return System.currentTimeMillis() - update > time; + return update != 0 && System.currentTimeMillis() - update > time; } -// -// public void click(int plusX, int plusY) { -// API.mouseClick(x + plusX, y + plusY); -// } - - public boolean show(boolean value) { - if (value != visible) { + public void click(int plusX, int plusY) { + API.mouseClick(x + plusX, y + plusY); + } - if (System.currentTimeMillis() - 1000 > time) { - API.mouseClick((int) minimized.x + 5, (int) minimized.y + 5); - time = System.currentTimeMillis(); - } + public void hover(int plusX, int plusY) { + API.mouseMove(x + plusX, y + plusY); + } + public boolean show(boolean value) { + if (trySetShowing(value)) { + if (minimized.address != 0) API.mouseClick((int) minimized.x + 5, (int) minimized.y + 5); return false; } + return isAnimationDone(); + } - return System.currentTimeMillis() - 1000 > time; + /** + * @param value Desired visibility status + * @return If action should be taken to change the visibility status + */ + public boolean trySetShowing(boolean value) { + if (value != visible && isAnimationDone()) { + time = System.currentTimeMillis(); + return true; + } + return false; + } + + public boolean isAnimationDone() { + return !isTweening && System.currentTimeMillis() - 1000 > time; } + } \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/Health.java b/src/main/java/com/github/manolo8/darkbot/core/objects/Health.java index 77f6495d..e9aa7edc 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/Health.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/Health.java @@ -1,33 +1,48 @@ package com.github.manolo8.darkbot.core.objects; import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.objects.itf.HealthHolder; import static com.github.manolo8.darkbot.Main.API; -public class Health extends Updatable { +public class Health extends Updatable implements HealthHolder { public int hp; public int maxHp; + public int hull; + public int maxHull; public int shield; public int maxShield; - public long lastIncreased; - public long lastDecreased; + private long hpLastIncreased, hpLastDecreased, + hullLastIncreased, hullLastDecreased, + shieldLastIncreased, shieldLastDecreased; @Override public void update() { - int hpLast = hp; + int hpLast = hp, maxHpLast = maxHp, + hullLast = hp, maxHullLast = maxHp, + shieldLast = shield, maxShieldLast = maxShield; hp = readIntFromIntHolder(48); maxHp = readIntFromIntHolder(56); + hull = readIntFromIntHolder(64); + maxHull = readIntFromIntHolder(72); shield = readIntFromIntHolder(80); maxShield = readIntFromIntHolder(88); - if (hpLast > hp) { - lastDecreased = System.currentTimeMillis(); - } else if (hpLast < hp) { - lastIncreased = System.currentTimeMillis(); + if (maxHpLast == maxHp && hpLast != hp) { + if (hpLast > hp) hpLastDecreased = System.currentTimeMillis(); + else hpLastIncreased = System.currentTimeMillis(); + } + if (maxHullLast == maxHull && hullLast != hull) { + if (hullLast > hull) hullLastDecreased = System.currentTimeMillis(); + else hullLastIncreased = System.currentTimeMillis(); + } + if (maxShieldLast == maxShield && shieldLast != shield) { + if (shieldLast > shield) shieldLastDecreased = System.currentTimeMillis(); + else shieldLastIncreased = System.currentTimeMillis(); } } @@ -43,11 +58,58 @@ public double shieldPercent() { return maxShield == 0 ? 1 : ((double) shield / (double) maxShield); } - public boolean isDecreasedIn(int time) { - return System.currentTimeMillis() - lastDecreased < time; + public boolean hpDecreasedIn(int time) { + return System.currentTimeMillis() - hpLastDecreased < time; + } + + public boolean hpIncreasedIn(int time) { + return System.currentTimeMillis() - hpLastIncreased < time; + } + + public boolean hullDecreasedIn(int time) { + return System.currentTimeMillis() - hullLastDecreased < time; + } + + public boolean hullIncreasedIn(int time) { + return System.currentTimeMillis() - hullLastIncreased < time; } - public boolean isIncreasedIn(int time) { - return System.currentTimeMillis() - lastIncreased < time; + public boolean shDecreasedIn(int time) { + return System.currentTimeMillis() - shieldLastDecreased < time; + } + + public boolean shIncreasedIn(int time) { + return System.currentTimeMillis() - shieldLastIncreased < time; + } + + + @Override + public int getHp() { + return hp; + } + + @Override + public int getMaxHp() { + return maxHp; + } + + @Override + public int getHull() { + return hull; + } + + @Override + public int getMaxHull() { + return maxHull; + } + + @Override + public int getShield() { + return shield; + } + + @Override + public int getMaxShield() { + return maxShield; } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/LocationInfo.java b/src/main/java/com/github/manolo8/darkbot/core/objects/LocationInfo.java index 26496a78..19d3b38a 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/LocationInfo.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/LocationInfo.java @@ -8,37 +8,54 @@ public class LocationInfo extends Updatable { - public final Location now; - public final Location last; + public Location now = new Location(); + public Location last = new Location(); + public Location past = new Location(); + private long lastUpdate; public double angle; public double speed; + public LocationInfo() {} + public LocationInfo(long address) { this.address = address; - - this.now = new Location(); - this.last = new Location(); } public LocationInfo(double x, double y) { - this(0); - this.now.x = x; this.now.y = y; } @Override public void update() { + if (address == 0) return; + double newX = API.readMemoryDouble(address + 32); + double newY = API.readMemoryDouble(address + 40); + updatePosition(newX, newY); + } + + public void updatePosition(double newX, double newY) { + // Update only if both x and y changed, or >100 ms since last update + if ((newX == now.x || newY == now.y) && System.currentTimeMillis() - lastUpdate < 100) return; + past.x = last.x; + past.y = last.y; last.x = now.x; last.y = now.y; - now.x = API.readMemoryDouble(address + 32); - now.y = API.readMemoryDouble(address + 40); + now.x = newX; + now.y = newY; - angle = now.angle(last); - speed = now.distance(last) * 10; + if (isMoving() && !(last.x == 0 && last.y == 0)) { + angle = now.angle(past); + double newSpeed = now.distance(last) / (System.currentTimeMillis() - lastUpdate); + speed = Math.min(1, speed * 0.75 + newSpeed * 0.25); // In-game speed of 560 is a 0.56 speed here, limit to 1000. + } else { + speed = 0; + } + + lastUpdate = System.currentTimeMillis(); } public Location destinationInTime(long time) { @@ -46,7 +63,7 @@ public Location destinationInTime(long time) { Location destination = new Location(); if (last.x != 0 && last.y != 0) { - double move = speed * (time / 1000); + double move = speed * time; destination.x = now.x + Math.cos(angle) * move; destination.y = now.y + Math.sin(angle) * move; @@ -66,15 +83,19 @@ public double distance(LocationInfo locationInfo) { return now.distance(locationInfo.now); } + public double distance(Location location) { + return now.distance(location); + } + public double distance(Entity entity) { return now.distance(entity.locationInfo.now); } public boolean isLoaded() { - return now.x != 0 && now.y != 0; + return address != 0; } public boolean isMoving() { - return !now.equals(last); + return !now.equals(last) || !now.equals(past); } } \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/Map.java b/src/main/java/com/github/manolo8/darkbot/core/objects/Map.java index 17a27013..27b86a4c 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/Map.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/Map.java @@ -1,46 +1,26 @@ package com.github.manolo8.darkbot.core.objects; -import com.github.manolo8.darkbot.core.entities.Portal; - -import java.util.HashMap; - public class Map { public int id; - public String name; - public Portal[] portals; - public HashMap distances; + public String name, shortName; public boolean pvp; + public boolean gg; - public Map(int id, String name, boolean pvp, Portal[] portals) { + public Map(int id, String name, boolean pvp, boolean gg) { + this(id, name, name, pvp, gg); + } + + public Map(int id, String name, String shortName, boolean pvp, boolean gg) { this.id = id; this.name = name; - this.portals = portals; - this.distances = new HashMap<>(); + this.shortName = shortName; this.pvp = pvp; + this.gg = gg; } - public void recursiveDistance() { - putDistance(this, 0); - } - - private void putDistance(Map map, int distance) { - if (distances.containsKey(map)) { - int value = distances.get(map); - if (value > distance) { - distances.put(map, distance); - - for (Portal portal : portals) { - portal.target.putDistance(map, distance + 1); - } - - } - } else { - distances.put(map, distance); - - for (Portal portal : portals) { - portal.target.putDistance(map, distance + 1); - } - } + @Override + public String toString() { + return name; } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/OreTradeGui.java b/src/main/java/com/github/manolo8/darkbot/core/objects/OreTradeGui.java new file mode 100644 index 00000000..b1923ddc --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/OreTradeGui.java @@ -0,0 +1,40 @@ +package com.github.manolo8.darkbot.core.objects; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.entities.BasePoint; +import com.github.manolo8.darkbot.core.utils.Drive; + +public class OreTradeGui extends Gui { + + private static final int SELLING_X_OFFSET = 80; + private Drive drive; + + public OreTradeGui(Main main) { + this.drive = main.hero.drive; + } + + public void sellOre(Ore ore) { + click(45 + ore.getXOffset(), 175); + } + + public boolean showTrade(boolean show, BasePoint base) { + if (trySetShowing(show)) { + if (show) { + base.clickable.setRadius(800); + drive.clickCenter(true, base.locationInfo.now); + base.clickable.setRadius(0); + } else click(8, 8); + return false; + } + return isAnimationDone(); + } + + public enum Ore { + PROMETIUM, ENDRIUM, TERBIUM, PROMETID, DURANIUM, PROMERIUM, SEPROM, PALLADIUM, OSMIUM; + + int getXOffset() { + return ordinal() * SELLING_X_OFFSET; + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/Point.java b/src/main/java/com/github/manolo8/darkbot/core/objects/Point.java index 4abc5f61..e8540440 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/Point.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/Point.java @@ -1,19 +1,16 @@ package com.github.manolo8.darkbot.core.objects; -import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; import static com.github.manolo8.darkbot.Main.API; -public class Point extends Updatable { +public class Point extends UpdatableAuto { public double x; public double y; - public Point(long address) { - this.address = address; - } - public void update() { + if (address == 0) return; this.x = API.readMemoryDouble(address + 32); this.y = API.readMemoryDouble(address + 40); } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/ShipInfo.java b/src/main/java/com/github/manolo8/darkbot/core/objects/ShipInfo.java index 5d0b9f54..b4bc0b3d 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/ShipInfo.java +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/ShipInfo.java @@ -7,19 +7,23 @@ public class ShipInfo extends Updatable { public int speed; + public double angle; public long target; - - public ShipInfo() { - } - - public ShipInfo(long address) { - this.address = address; - } + private long keepTargetTime; + public LocationInfo destination = new LocationInfo(); @Override public void update() { - target = API.readMemoryLong(address + 112); + long newTarget = API.readMemoryLong(address + 112); + if (newTarget != 0 || keepTargetTime > System.currentTimeMillis()) { + target = newTarget; + if (target != 0) keepTargetTime = System.currentTimeMillis() + 500; + } + angle = Math.toRadians(API.readMemoryInt(API.readMemoryLong(address + 48) + 32)); speed = API.readMemoryInt(API.readMemoryLong(address + 72) + 40); + + destination.update(API.readMemoryLong(address + 96)); + destination.update(); } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/TargetedOfferGui.java b/src/main/java/com/github/manolo8/darkbot/core/objects/TargetedOfferGui.java new file mode 100644 index 00000000..a42fe593 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/TargetedOfferGui.java @@ -0,0 +1,42 @@ +package com.github.manolo8.darkbot.core.objects; + +import static com.github.manolo8.darkbot.Main.API; + +public class TargetedOfferGui extends Gui { + + private boolean initialized; + + public void update() { + super.update(); + if (address == 0) return; + + x = (int) API.readMemoryDouble(address + 0x250); + y = (int) API.readMemoryDouble(address + 0x258); + + if (!initialized) { // Initialized sets initial time to wait for animation when first displayed + time = System.currentTimeMillis(); + initialized = true; + } + visible = true; + } + + @Override + public void reset() { + super.reset(); + initialized = false; + } + + public boolean show(boolean value) { + if (value) throw new UnsupportedOperationException("Can't set showing a targeted offer!"); + if (trySetShowing(false)) { + click(width - 3, 3); + return false; + } + return isAnimationDone(); + } + + @Override + public boolean isAnimationDone() { + return System.currentTimeMillis() - 2000 > time; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/BoosterProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/BoosterProxy.java new file mode 100644 index 00000000..6c0e8646 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/BoosterProxy.java @@ -0,0 +1,111 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import com.github.manolo8.darkbot.core.utils.ByteUtils; +import com.github.manolo8.darkbot.utils.Time; + +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static com.github.manolo8.darkbot.Main.API; +import static com.github.manolo8.darkbot.Main.UPDATE_LOCKER; + +public class BoosterProxy extends Updatable { + public List boosters = new ArrayList<>(); + + private final ObjArray boostersArr = ObjArray.ofVector(true); + + @Override + public void update() { + long data = API.readMemoryLong(address + 48) & ByteUtils.FIX; + + boostersArr.update(API.readMemoryLong(data + 0x48)); + synchronized (UPDATE_LOCKER) { + boostersArr.sync(boosters, Booster::new, null); + } + } + + public static class Booster extends UpdatableAuto { + public double amount, cd; + public String category, name; + public BoosterCategory cat; + + private final ObjArray subBoostersArr = ObjArray.ofVector(true); + + @Override + public void update() { + this.category = API.readMemoryString(address, 0x20); + this.cat = BoosterCategory.of(category); + this.name = API.readMemoryString(address, 0x40); //0x48 description; + this.amount = API.readMemoryDouble(address + 0x50); + this.subBoostersArr.update(API.readMemoryLong(address + 0x30)); + + double min = Double.POSITIVE_INFINITY, curr; + for (int i = 0; i < subBoostersArr.getSize(); i++) + if ((curr = API.readMemoryDouble(subBoostersArr.get(i), 0x30, 0x38)) > 0 && curr < min) min = curr; + this.cd = min; + } + + public String toSimpleString() { + return String.format("%3s %2.0f%% %s", Time.secondsToShort(cd), amount, cat.getSmall(category)); + } + + public Color getColor() { + return cat.getColor(); + } + } + + private enum BoosterCategory { + ABILITY_COOLDOWN_TIME ("CD" , new Color(0xFFC000)), + DAMAGE ("DMG" , new Color(0xFD0400)), + EXPERIENCE_POINTS ("EXP" , new Color(0xF77800)), + HITPOINTS ("HP" , new Color(0x049104)), + HONOUR_POINTS ("HON" , new Color(0xFF8080)), + REPAIR ("REP" , new Color(0xA93DE4)), + COLLECT_RESOURCES ("RES" , new Color(0xEAD215)), + SHIELD ("SHD" , new Color(0x69EBFF)), + SHIELD_REGENERATION ("SHDR" , new Color(0x3B64BD)), + AMOUNT ("AMT" , new Color(0xFFCC00)), + COLLECT_RESOURCES_NEWBIE("DBL" , new Color(0xFFF3CF)), + CHANCE ("CHN" , new Color(0xFFD100)), + EVENT_AMOUNT ("EVT AM", new Color(0x05B6E3)), + EVENT_CHANCE ("EVT CH", new Color(0x00C6EE)), + SPECIAL_AMOUNT ("SP AM" , new Color(0xFFFFFF)), + UNKNOWN ("?" , new Color(0x808080)) { + @Override + public String getSmall(String category) { + return Arrays.stream(category.split("_")) + .map(str -> str.length() <= 3 ? str : str.substring(0, 3)) + .collect(Collectors.joining(" ")); + } + }; + + private final String small; + private final Color color; + + BoosterCategory(String small, Color color) { + this.small = small; + this.color = color; + } + + public String getSmall(String category) { + return this.small; + } + public Color getColor() { + return this.color; + } + + public static BoosterCategory of(String category) { + for (BoosterCategory cat : BoosterCategory.values()) { + if (cat.name().equalsIgnoreCase(category)) return cat; + } + return UNKNOWN; + } + + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java new file mode 100644 index 00000000..e00cb393 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChatProxy.java @@ -0,0 +1,103 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import com.github.manolo8.darkbot.core.utils.ByteUtils; +import com.github.manolo8.darkbot.utils.LogUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.github.manolo8.darkbot.Main.API; + +public class ChatProxy extends Updatable { + public List chats = new ArrayList<>(); + + private ObjArray chatsArr = ObjArray.ofVector(true); + private Map streams = new HashMap<>(); + + private int clanLength = 7, nameLength = 10; + + @Override + public void update() { + if (!ConfigEntity.INSTANCE.getConfig().MISCELLANEOUS.LOG_CHAT) return; + long data = API.readMemoryLong(address + 48) & ByteUtils.FIX; + + this.chatsArr.update(API.readMemoryLong(data + 64)); + if (chatsArr.getSize() > 10) return; + this.chatsArr.sync(chats, Chat::new, null); + + for (Chat chat : chats) { + if (chat.messagesArr.getSize() > 150) continue; + chat.messagesArr.forEachIncremental(ptr -> writeToFile(chat.chatName, new Message(ptr))); + } + } + + private void writeToFile(String chatName, Message message) { + try { + OutputStream os = getOrCreateStream(chatName); + if (os == null) return; + + os.write(formatMessage(message).getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String formatMessage(Message message) { + if (message.clanTag.length() + 2 > clanLength) clanLength = message.clanTag.length() + 2; + if (message.username.length() > nameLength) nameLength = message.username.length(); + + return String.format("[%s] |%-7s, %-9s| %-" + clanLength + "s%-" + nameLength + "s: %s" + System.lineSeparator(), + LocalDateTime.now().format(LogUtils.LOG_DATE), + message.role, + message.userId, + "[" + message.clanTag + "]", + message.username, + message.message); + } + + private OutputStream getOrCreateStream(String chatName) { + return this.streams.computeIfAbsent(chatName, LogUtils::createLogFile); + } + + public static class Chat extends UpdatableAuto { + public String chatName; + //public List messages = new ArrayList<>(); + + private ObjArray messagesArr = ObjArray.ofVector(true); + + @Override + public void update() { + this.chatName = API.readMemoryString(address, 56); + + this.messagesArr.update(API.readMemoryLong(address + 80)); + //this.messagesArr.sync(messages, Message::new, null); + } + } + + public static class Message extends UpdatableAuto { + public String message, username, role, clanTag, globalId, userId; + + public Message() {} + private Message(long address) { update(address); } + + @Override + public void update() { + this.message = API.readMemoryString(address, 40); + this.username = API.readMemoryString(address, 56, 32); + this.role = API.readMemoryString(address, 56, 40); + this.clanTag = API.readMemoryString(address, 56, 48); + this.globalId = API.readMemoryString(address, 56, 56); + this.userId = API.readMemoryString(address, 56, 64); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChrominProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChrominProxy.java new file mode 100644 index 00000000..9daff073 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/ChrominProxy.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import static com.github.manolo8.darkbot.Main.API; + +public class ChrominProxy extends Updatable { + + public double currAmt, maxAmt; + + @Override + public void update() { + if (address == 0) return; + + long data = API.readMemoryLong(address + 48) & ByteUtils.FIX; + + this.maxAmt = API.readMemoryDouble(data + 48); + this.currAmt = API.readMemoryDouble(data + 40); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/EscortProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/EscortProxy.java new file mode 100644 index 00000000..87d1c218 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/EscortProxy.java @@ -0,0 +1,18 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import static com.github.manolo8.darkbot.Main.API; + +public class EscortProxy extends Updatable { + public double time; + public int keys; + + public void update() { + long data = API.readMemoryLong(address + 48) & ByteUtils.FIX; + + this.keys = API.readMemoryInt(API.readMemoryLong(data + 88) + 40); + this.time = API.readMemoryDouble(API.readMemoryLong(data + 72) + 56); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/EternalGateProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/EternalGateProxy.java new file mode 100644 index 00000000..8d1a1114 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/EternalGateProxy.java @@ -0,0 +1,59 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.github.manolo8.darkbot.Main.API; + +public class EternalGateProxy extends Updatable { + public int keys, boosterPoints, currentWave, furthestWave; + + public List activeBoosters = new ArrayList<>(); + public List boostersOptions = new ArrayList<>(); + + private ObjArray activeBoostersArr = ObjArray.ofVector(true); + private ObjArray boostersOptionsArr = ObjArray.ofVector(true); + + @Override + public void update() { + if (address == 0) return; + + long data = API.readMemoryLong(address + 48) & ByteUtils.FIX; + + this.currentWave = API.readMemoryInt(data + 0x40); + this.furthestWave = API.readMemoryInt(data + 0x44); + this.keys = API.readMemoryInt(API.readMemoryLong(data + 0x58) + 0x28); + this.boosterPoints = API.readMemoryInt(API.readMemoryLong(data + 0x60) + 0x28); + + this.activeBoostersArr.update(API.readMemoryLong( data + 0x68)); + this.boostersOptionsArr.update(API.readMemoryLong(data + 0x70)); + + this.activeBoostersArr.sync(activeBoosters, Booster::new, null); + this.boostersOptionsArr.sync(boostersOptions, Booster::new, null); + } + + public static class Booster extends UpdatableAuto { + public int percentage; + public String category; + + @Override + public void update() { + this.percentage = API.readMemoryInt(address + 0x20); + this.category = API.readMemoryString(API.readMemoryLong(address + 0x28)); + } + + @Override + public String toString() { + return "Booster{" + + "percentage=" + percentage + + ", category='" + category + '\'' + + '}'; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/LogMediator.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/LogMediator.java new file mode 100644 index 00000000..7bd20042 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/LogMediator.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import com.github.manolo8.darkbot.core.utils.Lazy; + +import static com.github.manolo8.darkbot.Main.API; + +public class LogMediator extends Updatable { + public final Lazy logs = new Lazy.NoCache<>(); // Can't cache the value, same log could appear twice + + private ObjArray messageBuffer = ObjArray.ofArrStr(); + + public LogMediator() { + this.logs.add(System.out::println); + } + + @Override + public void update() { + messageBuffer.update(API.readMemoryLong(address + 0x60)); + if (messageBuffer.size <= 0 || 50 < messageBuffer.size) return; + + messageBuffer.forEachIncremental(this::handleLogMessage); + } + + private void handleLogMessage(long pointer) { + String val = API.readMemoryString(API.readMemoryLong(pointer + 0x28)); + if (val != null && !val.trim().isEmpty()) logs.send(val); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/SlotBarsProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/SlotBarsProxy.java new file mode 100644 index 00000000..91a19cca --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/SlotBarsProxy.java @@ -0,0 +1,20 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.objects.slotbars.CategoryBar; +import com.github.manolo8.darkbot.core.objects.slotbars.SlotBar; + +import static com.github.manolo8.darkbot.Main.API; + +public class SlotBarsProxy extends Updatable { + public CategoryBar categoryBar = new CategoryBar(); + public SlotBar standardBar = new SlotBar(); + //public SlotBar premiumBar = new SlotBar(); //104 + //public SlotBar proActionBar = new SlotBar(); //112 + + @Override + public void update() { + this.categoryBar.update(API.readMemoryLong(address + 88)); + this.standardBar.update(API.readMemoryLong(address + 96)); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/facades/StatsProxy.java b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/StatsProxy.java new file mode 100644 index 00000000..1577bb11 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/facades/StatsProxy.java @@ -0,0 +1,23 @@ +package com.github.manolo8.darkbot.core.objects.facades; + +import com.github.manolo8.darkbot.core.itf.Updatable; + +import static com.github.manolo8.darkbot.Main.API; + +public class StatsProxy extends Updatable { + private int fps, memory; + + @Override + public void update() { + this.fps = API.readMemoryInt(address, 80, 32); + this.memory = (int) API.readMemoryDouble(address, 80, 56); + } + + public int getFps() { + return fps; + } + + public int getMemory() { + return memory; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/group/Group.java b/src/main/java/com/github/manolo8/darkbot/core/objects/group/Group.java new file mode 100644 index 00000000..87e0cb65 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/group/Group.java @@ -0,0 +1,69 @@ +package com.github.manolo8.darkbot.core.objects.group; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; + +import java.util.ArrayList; +import java.util.List; + +import static com.github.manolo8.darkbot.Main.API; + +public class Group extends UpdatableAuto { + private final HeroManager hero; + + public List members = new ArrayList<>(); + public GroupMember selectedMember = new GroupMember(); + + public int id; + public int size; + public int maxSize; + public boolean isOpen; // if the group is open to allowing anyone to invite + public boolean isLeader; + + private ObjArray membersPtr = ObjArray.ofVector(true); + + public Group(HeroManager hero) { + this.hero = hero; + } + + public boolean isValid() { + return id != 0 && size != 0 && maxSize == 8; + } + + @Override + public void update() { + id = API.readMemoryInt(address + 0x1F); + size = API.readMemoryInt(address + 0x23); + maxSize = API.readMemoryInt(address + 0x27); + isOpen = API.readMemoryBoolean(address + 0x2B); + + if (!isValid()) return; + + long selectedAddr = API.readMemoryLong(address + 0x3F); + + membersPtr.update(API.readMemoryLong(address + 0x37)); + + List filtered; + synchronized (Main.UPDATE_LOCKER) { + filtered = membersPtr.sync(members, GroupMember::new, m -> m.id != hero.id); + } + isLeader = filtered.stream().map(h -> h.isLeader).findFirst().orElse(false); + selectedMember = members.stream().filter(m -> selectedAddr == m.address).findFirst().orElse(null); + } + + public GroupMember getMember(int id) { + for (GroupMember member : members) { + if (member.id == id) return member; + } + return null; + } + + public int indexOf(GroupMember member) { + for (int i = 0; i < size; i++) + if (members.get(i) == member) return i; + return -1; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/group/GroupMember.java b/src/main/java/com/github/manolo8/darkbot/core/objects/group/GroupMember.java new file mode 100644 index 00000000..8c70624e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/group/GroupMember.java @@ -0,0 +1,47 @@ +package com.github.manolo8.darkbot.core.objects.group; + +import com.github.manolo8.darkbot.core.manager.StarManager; +import com.github.manolo8.darkbot.core.utils.Location; + +import static com.github.manolo8.darkbot.Main.API; + +public class GroupMember extends PartialGroupMember { + public Location location = new Location(); + public MemberInfo memberInfo = new MemberInfo(); + public MemberInfo targetInfo = new MemberInfo(); + + public int factionId; + public int level; + public int mapId; + public boolean isAttacked; + public boolean isCloacked; + public boolean isLeader; + public boolean isDead; + public boolean isLocked; // is selected by hero + + public String getMap() { + return StarManager.getInstance().byId(mapId).shortName; + } + + public String getDisplayText(boolean hideUsername) { + return getMap() + " " + (hideUsername ? "-Hidden-" : getUsername()) + " "; + } + + @Override + public void update() { + location.set(API.readMemoryInt(address + 0x38), API.readMemoryInt(address + 0x3C)); + memberInfo.update(API.readMemoryLong(address + 0x78)); + targetInfo.update(API.readMemoryLong(address + 0x80)); + + factionId = API.readMemoryInt(address + 0x24); + level = API.readMemoryInt(address + 0x28); + mapId = API.readMemoryInt(address + 0x34); + isAttacked = API.readMemoryBoolean(address + 0x44); + isCloacked = API.readMemoryBoolean(address + 0x48); + isLeader = API.readMemoryBoolean(address + 0x50); + isDead = API.readMemoryBoolean(address + 0x54); + isLocked = API.readMemoryBoolean(address + 0x60); + + super.update(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/group/Invite.java b/src/main/java/com/github/manolo8/darkbot/core/objects/group/Invite.java new file mode 100644 index 00000000..3effc973 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/group/Invite.java @@ -0,0 +1,28 @@ +package com.github.manolo8.darkbot.core.objects.group; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.manager.HeroManager; + +import static com.github.manolo8.darkbot.Main.API; + +public class Invite extends UpdatableAuto { + private final HeroManager hero; + + public PartialGroupMember inviter = new PartialGroupMember(); + public PartialGroupMember invited = new PartialGroupMember(); + public boolean incomming; + public boolean valid; + + public Invite(HeroManager hero) { + this.hero = hero; + } + + @Override + public void update() { + if (address == 0) return; + inviter.update(API.readMemoryLong(address + 0x20)); + invited.update(API.readMemoryLong(address + 0x28)); + valid = inviter.username != null && invited.username != null; + incomming = inviter.id != hero.id && invited.id == hero.id; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/group/MemberInfo.java b/src/main/java/com/github/manolo8/darkbot/core/objects/group/MemberInfo.java new file mode 100644 index 00000000..25b73423 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/group/MemberInfo.java @@ -0,0 +1,59 @@ +package com.github.manolo8.darkbot.core.objects.group; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.itf.HealthHolder; + +import static com.github.manolo8.darkbot.Main.API; + +public class MemberInfo extends UpdatableAuto implements HealthHolder { + public int shipType; + public int hp; + public int maxHp; + public int hull; + public int maxHull; + public int shield; + public int maxShield; + public String username; + + @Override + public void update() { + shipType = API.readMemoryInt(address + 0x20); + hp = API.readMemoryInt(address + 0x24); + maxHp = API.readMemoryInt(address + 0x28); + hull = API.readMemoryInt(address + 0x2C); + maxHull = API.readMemoryInt(address + 0x30); + shield = API.readMemoryInt(address + 0x34); + maxShield = API.readMemoryInt(address + 0x38); + username = API.readMemoryString(API.readMemoryLong(address + 0x40)); + } + + @Override + public int getHp() { + return hp; + } + + @Override + public int getMaxHp() { + return maxHp; + } + + @Override + public int getHull() { + return hull; + } + + @Override + public int getMaxHull() { + return maxHull; + } + + @Override + public int getShield() { + return shield; + } + + @Override + public int getMaxShield() { + return maxShield; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/group/PartialGroupMember.java b/src/main/java/com/github/manolo8/darkbot/core/objects/group/PartialGroupMember.java new file mode 100644 index 00000000..30c10cc4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/group/PartialGroupMember.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.core.objects.group; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; + +import static com.github.manolo8.darkbot.Main.API; + +public class PartialGroupMember extends UpdatableAuto { + public int id; + public String username; + + public String getUsername() { + return username != null ? username : "..."; + } + + @Override + public void update() { + id = API.readMemoryInt(address + 0x20); + username = API.readMemoryString(API.readMemoryLong(address + 0x68)); + if (username != null && (username.isEmpty() || username.equals("ERROR"))) + username = null; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/itf/HealthHolder.java b/src/main/java/com/github/manolo8/darkbot/core/objects/itf/HealthHolder.java new file mode 100644 index 00000000..2fcf23e1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/itf/HealthHolder.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.core.objects.itf; + +public interface HealthHolder { + + int getHp(); + int getMaxHp(); + int getHull(); + int getMaxHull(); + int getShield(); + int getMaxShield(); + + default double hpPercent() { + return getMaxHp() == 0 ? 1 : ((double) getHp() / (double) getMaxHp()); + } + default double shieldPercent() { + return getMaxShield() == 0 ? 1 : ((double) getShield() / (double) getMaxShield()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/CategoryBar.java b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/CategoryBar.java new file mode 100644 index 00000000..6896814c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/CategoryBar.java @@ -0,0 +1,62 @@ +package com.github.manolo8.darkbot.core.objects.slotbars; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static com.github.manolo8.darkbot.Main.API; + +public class CategoryBar extends MenuBar { + public List categories = new ArrayList<>(); + + private final ObjArray categoriesArr = ObjArray.ofVector(true); + + @Override + public void update() { + super.update(); + this.categoriesArr.update(API.readMemoryLong(address + 56)); + this.categoriesArr.sync(this.categories, Category::new, null); + } + + public Category get(CategoryType type) { + String id = type.getId(); + for (Category category : categories) { + if (id.equals(category.categoryId)) return category; + } + return null; + } + + public static class Category extends UpdatableAuto { + public String categoryId; + public List items = new ArrayList<>(); + + private final ObjArray itemsArr = ObjArray.ofVector(true); + + @Override + public void update() { + this.categoryId = API.readMemoryString(address, 32); + this.itemsArr.update(API.readMemoryLong(address + 40)); + this.itemsArr.sync(this.items, Item::new, null); + } + } + + public enum CategoryType { + LASERS, + ROCKETS, + ROCKET_LAUNCHERS, + SPECIAL_ITEMS, + MINES, + CPUS, + BUY_NOW, + TECH_ITEMS, + SHIP_ABILITIES, + DRONE_FORMATIONS; + + public String getId() { + return name().toLowerCase(Locale.ROOT); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java new file mode 100644 index 00000000..97533c96 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/Item.java @@ -0,0 +1,65 @@ +package com.github.manolo8.darkbot.core.objects.slotbars; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; + +import static com.github.manolo8.darkbot.Main.API; + +public class Item extends UpdatableAuto { + // Only has relevant info if !isReady() + public final ItemTimer itemTimer = new ItemTimer(); + + public double quantity; + public boolean selected, buyable, activatable, available, visible; + public String id, counterType, actionStyle, iconLootId; + + @Override + public void update() { + this.buyable = API.readMemoryBoolean(address + 36); + this.activatable = API.readMemoryBoolean(address + 40); + this.selected = API.readMemoryBoolean(address + 44); + this.available = API.readMemoryBoolean(address + 48); + this.visible = API.readMemoryBoolean(address + 52); + //this.blocked = API.readMemoryBoolean(address + 56); // doesnt work + this.quantity = API.readMemoryDouble(address + 128); + + long tempAddr = API.readMemoryLong(address, 88, 40); + if (itemTimer.address != tempAddr) this.itemTimer.update(tempAddr); + this.itemTimer.update(); + } + + @Override + public void update(long address) { + if (this.address != address) { + this.id = API.readMemoryString(address, 64); + this.counterType = API.readMemoryString(address, 72); + this.actionStyle = API.readMemoryString(address, 80); + this.iconLootId = API.readMemoryString(address, 96); + } + super.update(address); + } + + public boolean isReady() { + return this.itemTimer.address == 0; + } + + public static class ItemTimer extends UpdatableAuto { + public double elapsed, startTime, itemDelay, availableIn; + + @Override + public void update() { + if (address == 0) return; + + this.elapsed = API.readMemoryDouble(address + 72); + this.availableIn = API.readMemoryDouble(address + 96); + } + + @Override + public void update(long address) { + this.address = address; + if (address == 0) return; + + this.startTime = API.readMemoryDouble(address + 80); + this.itemDelay = API.readMemoryDouble(address + 88); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/MenuBar.java b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/MenuBar.java new file mode 100644 index 00000000..d4f66ffd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/MenuBar.java @@ -0,0 +1,18 @@ +package com.github.manolo8.darkbot.core.objects.slotbars; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.Point; + +import static com.github.manolo8.darkbot.Main.API; + +public abstract class MenuBar extends UpdatableAuto { + public String barId, layoutId; + public Point barLocation = new Point(); + + @Override + public void update() { + this.barId = API.readMemoryString(address, 32); + this.layoutId = API.readMemoryString(address, 40); + this.barLocation.update(API.readMemoryLong(address + 48)); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/SlotBar.java b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/SlotBar.java new file mode 100644 index 00000000..9476e7cc --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/slotbars/SlotBar.java @@ -0,0 +1,50 @@ +package com.github.manolo8.darkbot.core.objects.slotbars; + +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; +import com.github.manolo8.darkbot.core.objects.Point; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import static com.github.manolo8.darkbot.Main.API; + +public class SlotBar extends MenuBar { + public boolean isVisible; + public Point stickOffset = new Point(); + public List slots = new ArrayList<>(); + + private final ObjArray slotsArr = ObjArray.ofVector(true); + + @Override + public void update() { + super.update(); + + this.isVisible = API.readMemoryBoolean(address + 56); + this.stickOffset.update(API.readMemoryLong(address + 72)); + + this.slotsArr.update(API.readMemoryLong(address + 64)); + this.slotsArr.sync(slots, Slot::new, null); + } + + public static class Slot extends UpdatableAuto { + public int slotNumber; + public boolean premium; //not sure + public String slotBarId; + public @Nullable Item item; + + @Override + public void update() { + this.slotNumber = API.readMemoryInt(address + 32); + this.premium = API.readMemoryBoolean(address + 36); + this.slotBarId = API.readMemoryString(address, 48); + long itemPtr = API.readMemoryLong(address + 40); + if (itemPtr == 0) item = null; + else { + if (item == null) item = new Item(); + item.update(itemPtr); + } + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/Array.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/Array.java deleted file mode 100644 index 53672c37..00000000 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/Array.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.github.manolo8.darkbot.core.objects.swf; - -import com.github.manolo8.darkbot.core.itf.Updatable; -import com.github.manolo8.darkbot.core.utils.ByteUtils; - -import static com.github.manolo8.darkbot.Main.API; - -public class Array extends Updatable { - - public long[] elements; - public int size; - - public Array(long address) { - this.address = address; - this.elements = new long[0]; - } - - @Override - public void update() { - int size = API.readMemoryInt(address + 56); - - if (size < 0 || size > 512) { - return; - } else { - this.size = size; - - if (elements.length < size) { - elements = new long[size]; - } - } - - long table = API.readMemoryLong(address + 48) + 16; - int length = size * 8; - - byte[] bytes = API.readMemory(table, length); - - int current = 0; - - for (int i = 0; i < length; i += 8) { - long value = ByteUtils.getLong(bytes, i) - 1; - //Can change at any time! - if (value != -1 && current < elements.length) { - elements[current++] = value; - } - } - } -} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/Dictionary.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/Dictionary.java deleted file mode 100644 index eb38f0eb..00000000 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/Dictionary.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.github.manolo8.darkbot.core.objects.swf; - -import com.github.manolo8.darkbot.core.BotInstaller; -import com.github.manolo8.darkbot.core.itf.Updatable; -import com.github.manolo8.darkbot.core.utils.ByteUtils; -import com.github.manolo8.darkbot.core.utils.Lazy; - -import java.util.HashMap; -import java.util.Objects; -import java.util.function.Consumer; - -import static com.github.manolo8.darkbot.Main.API; - -public class Dictionary extends Updatable { - - private HashMap> lazy; - - private Entry[] elements; - private String[] checks; - - public int size; - - private int lastFix; - - public Dictionary(long address) { - this.address = address; - this.elements = new Entry[0]; - } - - public void addLazy(String key, Consumer consumer) { - if (lazy == null) lazy = new HashMap<>(); - - Lazy lazy = this.lazy.get(key); - - if (lazy == null) { - lazy = new Lazy<>(); - this.lazy.put(key, lazy); - } - - lazy.add(consumer); - } - - @Override - public void update() { - - - long tableInfo = API.readMemoryLong(address + 32); - int size = API.readMemoryInt(tableInfo + 16); - long table = API.readMemoryLong(tableInfo + 8) - 4; - int length = (int) (Math.pow(2, API.readMemoryInt(tableInfo + 20)) * 4) + lastFix + 4; - - if (length > 4096 || length < 0 || size < 0 || size > 1024) return; - - if (elements.length < size) { - checks = new String[size]; - Entry[] temp = new Entry[size]; - System.arraycopy(elements, 0, temp, 0, elements.length); - elements = temp; - } - - lastFix = 0; - - byte[] bytes = API.readMemory(table, length); - - int current = 0; - int check = 0; - - for (int i = searchFix(bytes); i < length; i += 16) { - - long valueAddress = ByteUtils.getLong(bytes, i + 8) - 1; - - if (valueAddress >= -1 && valueAddress <= 9) continue; - - Entry entry; - - if (current >= size) { - break; - } else if (elements[current] == null) { - entry = elements[current] = new Entry(API.readMemoryString(ByteUtils.getLong(bytes, i) - 2), valueAddress); - send(entry); - } else if ((entry = elements[current]).value != valueAddress) { - checks[check++] = entry.key; - - entry.key = API.readMemoryString(ByteUtils.getLong(bytes, i) - 2); - entry.value = valueAddress; - - //send update - send(entry); - } - - System.out.println(entry.key); - - current++; - } - - if (this.size > current) { - while (this.size != current) { - checks[check++] = elements[--this.size].key; - elements[this.size] = null; - } - } - - this.size = current; - - if (check > 0) { - for (check--; check >= 0; check--) { - String str; - - if (!hasKey(str = checks[check])) - send(str, 0); - - checks[check] = null; - } - } - } - - private int searchFix(byte[] bytes) { - - if (lastFix >= 0 && bytes.length > lastFix && ByteUtils.getInt(bytes, lastFix) == BotInstaller.SEP) { - return lastFix + 4; - } else { - for (int i = 0; i < bytes.length; i++) { - if (ByteUtils.getInt(bytes, i) == BotInstaller.SEP) { - lastFix = i; - return bytes.length - 1; - } - } - } - - return bytes.length - 1; - } - - private void send(Entry entry) { - send(entry.key, entry.value); - } - - private void send(String key, long value) { - if (lazy != null) { - Lazy lazy = this.lazy.get(key); - - if (lazy != null && !(Objects.equals(lazy.value, value))) { - lazy.send(value); - } - } - } - - private boolean hasKey(String str) { - for (int i = 0; i < size; i++) { - Entry entry = elements[i]; - if (entry.key.equals(str)) - return true; - } - - return false; - } - - @Override - public void update(long address) { - super.update(address); - this.lastFix = 0; - } - - private class Entry { - - String key; - long value; - - public Entry(String key, long value) { - this.key = key; - this.value = value; - } - } -} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/IntArray.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/IntArray.java new file mode 100644 index 00000000..8779e98d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/IntArray.java @@ -0,0 +1,91 @@ +package com.github.manolo8.darkbot.core.objects.swf; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import static com.github.manolo8.darkbot.Main.API; + +/** + * Reads arrays with ints. + * Instead of VectorInt & ArrayInt + */ +public class IntArray extends Updatable { + private final int sizeOffset, tableOffset, bytesOffset; + private final boolean isArray, autoUpdatable; + + public int[] elements = new int[0]; + public int size; + + public IntArray(int sizeOffset, int tableOffset, int bytesOffset, boolean isArray, boolean autoUpdatable) { + this.sizeOffset = sizeOffset; + this.tableOffset = tableOffset; + this.bytesOffset = bytesOffset; + this.isArray = isArray; + this.autoUpdatable = autoUpdatable; + } + + /** + * Reads int Array {@code Array} + */ + public static IntArray ofArray() { + return ofArray(false); + } + + public static IntArray ofArray(boolean autoUpdatable) { + return new IntArray(0x28, 0x20, 0x10, true, autoUpdatable); + } + + /** + * Reads int Vector {@code Vector} + */ + public static IntArray ofVector() { + return ofVector(false); + } + + public static IntArray ofVector(long address) { + IntArray ofVec = ofVector(false); + ofVec.update(address); + return ofVec; + } + + public static IntArray ofVector(boolean autoUpdatable) { + return new IntArray(0x40, 0x30, 0x4, false, autoUpdatable); + } + + public int getSize() { + return Math.min(size, elements.length); + } + + public int get(int idx) { + return idx >= 0 && idx < getSize() ? elements[idx] : 0; + } + + public int getLast() { + return get(getSize() - 1); + } + + @Override + public void update() { + size = API.readMemoryInt(address + sizeOffset); + + if (size < 0 || size > 1024) return; + if (elements.length < size) elements = new int[(int) Math.min(size * 1.25, 1024)]; + + long table = API.readMemoryLong(address + tableOffset) + bytesOffset; + byte[] data = API.readMemory(table, size * getOffset()); + + for (int i = 0, offset = 0; i < size; i++, offset += getOffset()) { + elements[i] = ByteUtils.getInt(data, offset) >> (isArray ? 3 : 0); + } + } + + @Override + public void update(long address) { + super.update(address); + if (autoUpdatable) update(); + } + + private int getOffset() { + return isArray ? 8 : 4; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java new file mode 100644 index 00000000..a0639c87 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/ObjArray.java @@ -0,0 +1,110 @@ +package com.github.manolo8.darkbot.core.objects.swf; + +import com.github.manolo8.darkbot.core.utils.ByteUtils; + +import static com.github.manolo8.darkbot.Main.API; + +/** + * Reads arrays in flash. + * Instead of ArrayObj, VectorPtr & SpriteArray + */ +public class ObjArray extends SwfPtrCollection { + private final int sizeOffset, tableOffset, bytesOffset; + private final boolean autoUpdatable; + + public int size; + private long[] elements = new long[0]; + + protected ObjArray(int sizeOffset, int tableOffset, int bytesOffset, boolean autoUpdatable) { + this.sizeOffset = sizeOffset; + this.tableOffset = tableOffset; + this.bytesOffset = bytesOffset; + this.autoUpdatable = autoUpdatable; + } + + /** + * Probably for {@code Array} + */ + public static ObjArray ofArrStr() { + return ofArrStr(true); + } + + public static ObjArray ofArrStr(boolean autoUpdatable) { + return new ObjArray(0x28, 0x20, 0x10, autoUpdatable); + } + + /** + * Probably for {@code Array} + */ + public static ObjArray ofArrObj() { + return ofArrObj(true); + } + + public static ObjArray ofArrObj(boolean autoUpdatable) { + return new ObjArray(0x38, 0x20, 0x10, autoUpdatable); + } + + /** + * Reads children array of Sprite {@code Sprite} + */ + public static ObjArray ofSprite() { + return ofSprite(true); + } + + public static ObjArray ofSprite(boolean autoUpdatable) { + return new ObjArray(0x18, 0x8, 0x8, autoUpdatable); + } + + /** + * Reads {@code Vector} + */ + public static ObjArray ofVector() { + return ofVector(false); + } + + public static ObjArray ofVector(boolean autoUpdatable) { + return new ObjArray(0x38, 0x30, 0x10, autoUpdatable); + } + + public int getSize() { + return Math.min(size, elements.length); + } + + public long getPtr(int idx) { + return get(idx); + } + + public long get(int idx) { + return idx >= 0 && idx < getSize() ? elements[idx] : 0; + } + + public long getLast() { + return get(getSize() - 1); + } + + @Override + public void update() { + size = API.readMemoryInt(address + sizeOffset); + + if (size < 0 || size > 8192 || address == 0) return; + if (elements.length < size) elements = new long[Math.min((int) (size * 1.25), 8192)]; + + long table = API.readMemoryLong(address + tableOffset) + bytesOffset; + byte[] bytes = API.readMemory(table, size * 8); + + for (int i = 0, offset = 0; offset < bytes.length && i < size; offset += 8) { + long value = ByteUtils.getLong(bytes, offset); + elements[i++] = value & ByteUtils.FIX; //not sure if we should skip 0 values + } + } + + @Override + public void update(long address) { + super.update(isSprite() ? API.readMemoryLong(address, 0x48, 0x40) : address); + if (autoUpdatable) update(); + } + + private boolean isSprite() { + return this.sizeOffset == 0x18; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java new file mode 100644 index 00000000..e8d97562 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/PairArray.java @@ -0,0 +1,214 @@ +package com.github.manolo8.darkbot.core.objects.swf; + +import com.github.manolo8.darkbot.core.utils.ByteUtils; +import com.github.manolo8.darkbot.core.utils.Lazy; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static com.github.manolo8.darkbot.Main.API; + +/** + * Reads arrays with pair of values + * Instead of EntryArray & Dictionary + */ +public abstract class PairArray extends SwfPtrCollection { + private boolean autoUpdatable, ignoreEmpty = true; + + public int size; + + private Pair[] pairs = new Pair[0]; + private Map> lazy = new HashMap<>(); + + protected PairArray() {} + + /** + * Reads pairs of {@code Array} type + */ + public static PairArray ofArray() { + return new PairArray.Pairs(); + } + + /** + * Reads pairs of {@code Dictionary} type + */ + public static PairArray ofDictionary() { + return new PairArray.Dictionary(); + } + + public PairArray setAutoUpdatable(boolean updatable) { + this.autoUpdatable = updatable; + return this; + } + + public PairArray setIgnoreEmpty(boolean ignoreEmpty) { + this.ignoreEmpty = ignoreEmpty; + return this; + } + + public void addLazy(String key, Consumer consumer) { + addLazy(key, consumer, true); + } + + public void addLazy(String key, Consumer consumer, boolean cacheValue) { + this.lazy.computeIfAbsent(key, k -> (cacheValue ? new Lazy<>() : new Lazy.NoCache<>())).add(consumer); + } + + public int getSize() { + return Math.min(size, pairs.length); + } + + public long getPtr(int idx) { + return get(idx).value; + } + + public long getPtr(String key) { + Pair pair = get(key); + return pair != null ? pair.value : 0L; + } + + public Pair get(int idx) { + return idx >= 0 && idx < size && idx < pairs.length ? pairs[idx] : null; + } + + public Pair get(String key) { + for (Pair pair : pairs) if (pair.key.equals(key)) return pair; + return null; + } + + public boolean hasKey(String key) { + for (int i = 0; i < size && i < pairs.length; i++) + if (pairs[i] != null && pairs[i].key != null && pairs[i].key.equals(key)) return true; + return false; + } + + @Override + public void update(long address) { + super.update(address); + if (autoUpdatable) update(); + } + + private static class Pairs extends PairArray { + public boolean isInvalid(long addr) { + return addr == 0; + } + + public String getKey(long addr) { + if (isInvalid(addr)) return null; + String key = API.readMemoryString(addr); + return key == null || key.trim().isEmpty() || key.equals("ERROR") ? null : key; + } + + public void update() { + if (super.lazy.isEmpty() && super.ignoreEmpty) return; + + size = API.readMemoryInt(address + 0x50); + + if (size < 0 || size > 1024) return; + if (super.pairs.length != size) super.pairs = Arrays.copyOf(super.pairs, size); + + long table = API.readMemoryLong(address + 0x48) & ByteUtils.FIX; + + String key = null; + for (int offset = 8, i = 0; offset < 8192 && i < size; offset += 8) { + if (key == null && (key = getKey(API.readMemoryLong(table + offset) & ByteUtils.FIX)) == null) continue; + + long value = API.readMemoryLong(table + (offset += 8)); + if (isInvalid(value)) continue; + + if (super.pairs[i] == null) super.pairs[i] = new Pair(key, value & ByteUtils.FIX); + else super.pairs[i++].set(key, value & ByteUtils.FIX); + key = null; + } + } + + } + + private static class Dictionary extends PairArray { + private String[] removed; + + @Override + public void update() { + if (address == 0) return; + + long tableInfo = API.readMemoryLong(address + 32); + + if (tableInfo == 0) return; + + int size = API.readMemoryInt(tableInfo + 16); + long table = align8(API.readMemoryLong(tableInfo + 8)); + int exp = API.readMemoryInt(tableInfo + 20); + int length = (int) (Math.pow(2, exp) * 4); + + if (length > 4096 || length < 0 || size < 0 || size > 1024) return; + + if (super.pairs.length < size) { + removed = new String[size]; + super.pairs = Arrays.copyOf(super.pairs, size); + } + + byte[] bytes = API.readMemory(table, length); + + int current = 0, remove = 0; + for (int offset = 0; offset < length && current < size; offset += 16) { + long keyAddr = ByteUtils.getLong(bytes, offset) - 2, valAddr = ByteUtils.getLong(bytes, offset + 8) - 1; + + if (keyAddr == -2 || (valAddr >= -2 && valAddr <= 9)) continue; + + Pair pair = super.pairs[current]; + if (pair == null) super.pairs[current] = new Pair(API.readMemoryString(keyAddr), valAddr); + else if (pair.value != valAddr) { + removed[remove++] = pair.key; + super.pairs[current].set(API.readMemoryString(keyAddr), valAddr); + } + current++; + } + + while (this.size > current) { + removed[remove++] = super.pairs[--this.size].key; + super.pairs[this.size] = null; + } + this.size = current; + + for (String str; remove > 0;) { + if (hasKey(str = removed[--remove])) continue; + Lazy l = super.lazy.get(str); + if (l != null) l.send(0L); + } + } + + private long align8(long value) { + long aligned = value + 8 - (value & 0b1111); + return aligned <= value ? aligned + 8 : aligned; + } + } + + public class Pair { + public String key; + public long value; + + private Pair() {} + private Pair(String key, long value) { + set(key, value); + } + + + private void set(String index, long value) { + this.key = index; + this.value = value; + + Lazy l = lazy.get(index); + if (l != null) l.send(value); + } + + @Override + public String toString() { + return "Pair{" + + "key='" + key + '\'' + + ", value=" + value + + '}'; + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/SwfPtrCollection.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/SwfPtrCollection.java new file mode 100644 index 00000000..7488c2e7 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/SwfPtrCollection.java @@ -0,0 +1,95 @@ +package com.github.manolo8.darkbot.core.objects.swf; + +import com.github.manolo8.darkbot.core.itf.Updatable; +import com.github.manolo8.darkbot.core.itf.UpdatableAuto; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * Represents a collection of pointers in SWF. + * Contains an utility method for syncing the pointers with java object collections. + */ +public abstract class SwfPtrCollection extends Updatable { + private long lastPointer; + + /** + * @return size of the SWF collection + */ + public abstract int getSize(); + + /** + * @param i The index to search + * @return The pointer the index points to to + */ + public abstract long getPtr(int i); + + /** + * Iterates over pointers in collection + * starting from last iterated + 1 + *

+ * For example, if collection wasn't changed, + * order and pointers are the same, + * consumer wont be executed even once. + * + * @param consumer to execute + */ + public void forEachIncremental(Consumer consumer) { + for (int i = indexOf(lastPointer) + 1; i < getSize(); i++) + consumer.accept(lastPointer = getPtr(i)); + } + + /** + * Iterates over all pointers in collection. + * @param consumer to execute + */ + public void forEach(Consumer consumer) { + for (int i = 0; i < getSize(); i++) + consumer.accept(getPtr(i)); + } + + /** + * Search backwards pointer's index in collection + * + * @param value pointer to search + * @return index of pointer or -1 if doesnt exist + */ + public int indexOf(long value) { + for (int i = getSize() - 1; i >= 0; i--) + if (value == getPtr(i)) return i; + return -1; + } + + /** + * Syncs a java list to this SWF collection. + * @param list The java list to sync + * @param constructor The constructor for new instances of the object + * @param filter The filter to apply, if any objects should be ignored from the list + * @param The type the pointer is mapped to in java + * @return The leftover items that didn't match the filter + */ + public List sync(List list, + Supplier constructor, + Predicate filter) { + int currSize = getSize(), listIdx = 0; + List ignored = new ArrayList<>(); + for (int arrIdx = 0; arrIdx < currSize; listIdx++, arrIdx++) { + boolean newItem = list.size() <= listIdx; + T item = newItem ? constructor.get() : list.get(listIdx); + item.update(getPtr(arrIdx)); + if (filter != null && !filter.test(item)) { + if (!newItem) list.remove(listIdx); + ignored.add(item); + listIdx--; + continue; + } + if (newItem) list.add(item); + } + while (list.size() > listIdx) + list.remove(list.size() - 1); + return ignored; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/VectorInt.java b/src/main/java/com/github/manolo8/darkbot/core/objects/swf/VectorInt.java deleted file mode 100644 index f6bf5047..00000000 --- a/src/main/java/com/github/manolo8/darkbot/core/objects/swf/VectorInt.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.github.manolo8.darkbot.core.objects.swf; - -import com.github.manolo8.darkbot.core.itf.Updatable; -import com.github.manolo8.darkbot.core.utils.ByteUtils; - -import static com.github.manolo8.darkbot.Main.API; - -public class VectorInt extends Updatable { - - public int[] elements; - public int size; - - public VectorInt() { - this.elements = new int[0]; - this.size = 0; - } - - public VectorInt(long address) { - this(); - this.address = address; - } - - @Override - public void update() { - - int size = API.readMemoryInt(address + 64); - - if (size < 1 || size > 512) return; - - if (this.size < size) { - elements = new int[size]; - } - - this.size = size; - - byte[] data = API.readMemory(API.readMemoryLong(address + 48) + 4, size * 4); - - int count = 0; - - for (int i = 0; i < data.length; i += 4) { - elements[count++] = ByteUtils.getInt(data, i); - } - - } - -} diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java b/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java index bc228fc6..d60612ac 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/ByteUtils.java @@ -1,16 +1,19 @@ package com.github.manolo8.darkbot.core.utils; public class ByteUtils { + public static final long FIX = 0xfffffffffff8L; public static int getInt(byte[] data, int offset) { - return data.length < offset + 4 ? 0 : ((data[offset + 3]) << 24) | + return data.length < offset + 4 ? 0 : + ((data[offset + 3]) << 24) | ((data[offset + 2] & 0xff) << 16) | ((data[offset + 1] & 0xff) << 8) | ((data[offset] & 0xff)); } public static long getLong(byte[] data, int offset) { - return data.length < offset + 8 ? 0 : (((long) data[offset + 7]) << 56) | + return data.length < offset + 8 ? 0 : + (((long) data[offset + 7]) << 56) | (((long) data[offset + 6] & 0xff) << 48) | (((long) data[offset + 5] & 0xff) << 40) | (((long) data[offset + 4] & 0xff) << 32) | @@ -20,23 +23,39 @@ public static long getLong(byte[] data, int offset) { (((long) data[offset] & 0xff)); } - public static byte[] getBytes(long... values) { + public static double getDouble(byte[] data, int offset) { + return Double.longBitsToDouble(getLong(data, offset)); + } - byte[] b = new byte[values.length * 8]; + public static byte[] getBytes(int... values) { + byte[] b = new byte[values.length * 4]; int i = 0; + for (int v : values) { + b[i++] = (byte) ((v) & 0xff); + b[i++] = (byte) ((v >>> 8) & 0xff); + b[i++] = (byte) ((v >>> 16) & 0xff); + b[i++] = (byte) ((v >>> 24) & 0xff); + } - for (long value : values) { - b[i++] = (byte) value; - b[i++] = (byte) (value >> 8); - b[i++] = (byte) (value >> 16); - b[i++] = (byte) (value >> 24); - b[i++] = (byte) (value >> 32); - b[i++] = (byte) (value >> 40); - b[i++] = (byte) (value >> 48); - b[i++] = (byte) (value >> 56); + return b; + } + + public static byte[] getBytes(long... values) { + byte[] b = new byte[values.length * 8]; + + int i = 0; + for (long v : values) { + b[i++] = (byte) v; + b[i++] = (byte) (v >> 8); + b[i++] = (byte) (v >> 16); + b[i++] = (byte) (v >> 24); + b[i++] = (byte) (v >> 32); + b[i++] = (byte) (v >> 40); + b[i++] = (byte) (v >> 48); + b[i++] = (byte) (v >> 56); } return b; } -} +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/ClickPoint.java b/src/main/java/com/github/manolo8/darkbot/core/utils/ClickPoint.java new file mode 100644 index 00000000..135723d9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/ClickPoint.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.core.utils; + +public class ClickPoint { + public int x, y; + public ClickPoint(int x, int y) { + this.x = x; + this.y = y; + } + + public synchronized void set(int x, int y) { + this.x = x; + this.y = y; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/Drive.java b/src/main/java/com/github/manolo8/darkbot/core/utils/Drive.java index de47e643..6e2a1d02 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/Drive.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/Drive.java @@ -1,86 +1,156 @@ package com.github.manolo8.darkbot.core.utils; +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.ZoneInfo; import com.github.manolo8.darkbot.core.entities.Entity; import com.github.manolo8.darkbot.core.manager.HeroManager; import com.github.manolo8.darkbot.core.manager.MapManager; +import com.github.manolo8.darkbot.core.manager.MouseManager; import com.github.manolo8.darkbot.core.objects.LocationInfo; import com.github.manolo8.darkbot.core.utils.pathfinder.PathFinder; +import com.github.manolo8.darkbot.core.utils.pathfinder.PathPoint; +import com.github.manolo8.darkbot.utils.MathUtils; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; -import static java.lang.Math.min; import static java.lang.Math.random; public class Drive { - private final MapManager map; + private static final Random RANDOM = new Random(); + private boolean force = false; - private final LocationInfo heroLocation; + private final MapManager map; + private final MouseManager mouse; + private final HeroManager hero; + private final LocationInfo heroLoc; public PathFinder pathFinder; + public LinkedList paths = new LinkedList<>(); - private Location destination; + private Location tempDest, endLoc, lastSegment = new Location(); + + private long lastDirChange; + private long lastClick; + public long lastMoved; public Drive(HeroManager hero, MapManager map) { this.map = map; - this.heroLocation = hero.locationInfo; - this.pathFinder = new PathFinder(map.entities.obstacles); + this.mouse = new MouseManager(map); + this.hero = hero; + this.heroLoc = hero.locationInfo; + this.pathFinder = new PathFinder(map); } public void checkMove() { - - if (destination != null) { - pathFinder.createRote(heroLocation.now, destination); - destination = null; + // Path-finder changed and bot is already traveling, re-create route + if (endLoc != null && pathFinder.changed() && tempDest == null) tempDest = endLoc; + + boolean newPath = tempDest != null; + if (newPath) { // Calculate new path + if (endLoc == null) endLoc = tempDest; // Should never happen + paths = pathFinder.createRote(heroLoc.now, tempDest); + tempDest = null; } - if (pathFinder.isEmpty() || !heroLocation.isLoaded()) + if (paths.isEmpty() || !heroLoc.isLoaded()) return; - Location now = heroLocation.now; - Location destination = pathFinder.current(); - - double distance = now.distance(destination); - - if (!heroLocation.isMoving()) - map.translateMousePress(now.x, now.y); - - if (distance > 100) { - - distance = min(distance, 200); - - double angle = destination.angle(now); - - map.translateMouseMove( - Math.cos(angle) * distance + now.x, - Math.sin(angle) * distance + now.y - ); - + lastMoved = System.currentTimeMillis(); + + Location now = heroLoc.now, last = heroLoc.last, next = current(); + if (next == null) return; + newPath |= !next.equals(lastSegment); + if (newPath) { + // If direction roughly similar, and changed dir little ago, and you're still gonna be moving, ignore change + // This smooths out paths in short distances + if (next.distance(lastSegment) + (System.currentTimeMillis() - lastDirChange) < 500 && + hero.timeTo(now.distance(lastSegment)) > 50) return; + lastDirChange = System.currentTimeMillis(); + } + lastSegment = next; + + if (newPath || hero.timeTo(now.distance(next)) > 25) { + double dirAngle = next.angle(last), + maxDiff = Math.max(0.02, MathUtils.angleDiff(next.angle(Location.of(heroLoc.last, dirAngle + (Math.PI / 2), 100)), dirAngle)); + if (!newPath && heroLoc.isMoving() && MathUtils.angleDiff(heroLoc.angle, dirAngle) < maxDiff) { + if (System.currentTimeMillis() - lastDirChange > 2000) { + click(next); + lastDirChange = System.currentTimeMillis(); + } + return; + } + + if (!force && heroLoc.isMoving() && System.currentTimeMillis() - lastDirChange > 350) stop(false); + else { + if (!newPath && System.currentTimeMillis() - lastDirChange > 300) tempDest = endLoc; // Re-calculate path next tick + if (now.distance(next) < 100) click(Location.of(heroLoc.now, heroLoc.now.angle(next), 150)); + else click(next); + } } else { + synchronized (Main.UPDATE_LOCKER) { + paths.removeFirst(); + } + if (paths.isEmpty()) { + if (this.endLoc.equals(lastRandomMove)) lastRandomMove = null; + this.endLoc = this.tempDest = null; + } + } + } - pathFinder.currentCompleted(); + private Location current() { + if (paths.isEmpty()) return null; + PathPoint point = paths.getFirst(); + return new Location(point.x, point.y); + } - map.translateMouseMoveRelease( - destination.x, - destination.y - ); + private void click(Location loc) { + if (System.currentTimeMillis() - lastClick > 150) { + lastClick = System.currentTimeMillis(); + mouse.clickLoc(loc); } - } public boolean canMove(Location location) { - return pathFinder.canMove(location); + return !map.isOutOfMap(location.x, location.y) && pathFinder.canMove((int) location.x, (int) location.y); } - public void stop() { - map.translateMouseMoveRelease(heroLocation.now.x, heroLocation.now.y); + public double closestDistance(Location location) { + PathPoint closest = pathFinder.fixToClosest(new PathPoint((int) location.x, (int) location.y)); + return location.distance(closest.toLocation()); + } - if (!pathFinder.isEmpty()) { - pathFinder.path().clear(); + public double distanceBetween(Location loc, int x, int y) { + double sum = 0; + PathPoint begin = new PathPoint((int) loc.x, (int) loc.y); + for (PathPoint curr : pathFinder.createRote(begin, new PathPoint(x, y))) + sum += Math.sqrt(Math.pow(begin.x - curr.x, 2) + Math.pow(begin.y - curr.y, 2)); + return sum; + } + + public void toggleRunning(boolean running) { + this.force = running; + stop(true); + } + + public void stop(boolean current) { + if (heroLoc.isMoving() && current) { + Location stopLoc = heroLoc.now.copy(); + stopLoc.toAngle(heroLoc.now, heroLoc.last.angle(heroLoc.now), 100); + mouse.clickLoc(stopLoc); } + + endLoc = tempDest = null; + if (!paths.isEmpty()) paths = new LinkedList<>(); } - public void clickCenter(int times) { - for (int i = 0; i != times; i++) - map.translateMouseClick(heroLocation.now.x, heroLocation.now.y); + public void clickCenter(boolean single, Location aim) { + mouse.clickCenter(single, aim); } public void move(Entity entity) { @@ -92,18 +162,54 @@ public void move(Location location) { } public void move(double x, double y) { - destination = new Location(x, y); + Location newDir = new Location(x, y); + if (movingTo().distance(newDir) > 10) tempDest = endLoc = newDir; } + private Location lastRandomMove; + private List lastZones; + private int lastZoneIdx; public void moveRandom() { - move(random() * MapManager.internalWidth, random() * MapManager.internalHeight); + ZoneInfo area = map.preferred; + boolean sequential = hero.main.config.GENERAL.ROAMING.SEQUENTIAL; + + List zones = area == null ? Collections.emptyList() : + sequential ? area.getSortedZones() : area.getZones(); + boolean changed = !zones.equals(lastZones); + lastZones = zones; + + if ( hero.main.config.GENERAL.ROAMING.KEEP && !changed && lastRandomMove != null) { + move(lastRandomMove); + return; + } + + if (changed && !lastZones.isEmpty()) { + Location search = lastRandomMove != null ? lastRandomMove : movingTo(); + ZoneInfo.Zone closest = lastZones.stream().min(Comparator.comparingDouble(zone -> + zone.innerPoint(0.5, 0.5, MapManager.internalWidth, MapManager.internalHeight).distance(search))).orElse(null); + lastZoneIdx = lastZones.indexOf(closest); + } + + if (lastZones.isEmpty()) { + lastRandomMove = new Location(random() * MapManager.internalWidth, random() * MapManager.internalHeight); + } else { + if (lastZoneIdx >= lastZones.size()) lastZoneIdx = 0; + ZoneInfo.Zone zone = lastZones.get(sequential ? lastZoneIdx++ : RANDOM.nextInt(zones.size())); + + lastRandomMove = zone.innerPoint(random(), random(), MapManager.internalWidth, MapManager.internalHeight); + } + move(lastRandomMove); } public boolean isMoving() { - return !pathFinder.isEmpty() || heroLocation.isMoving(); + return !paths.isEmpty() || heroLoc.isMoving(); + } + + public Location movingTo() { + return endLoc == null ? heroLoc.now.copy() : endLoc.copy(); } public boolean isOutOfMap() { - return map.isOutOfMap(heroLocation.now.x, heroLocation.now.y); + return map.isOutOfMap(heroLoc.now.x, heroLoc.now.y); } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/EntityList.java b/src/main/java/com/github/manolo8/darkbot/core/utils/EntityList.java index 84eb1016..f4e69d04 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/EntityList.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/EntityList.java @@ -1,208 +1,158 @@ package com.github.manolo8.darkbot.core.utils; import com.github.manolo8.darkbot.Main; -import com.github.manolo8.darkbot.core.entities.*; +import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.core.entities.Barrier; +import com.github.manolo8.darkbot.core.entities.BasePoint; +import com.github.manolo8.darkbot.core.entities.BattleStation; +import com.github.manolo8.darkbot.core.entities.Box; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.entities.FakeNpc; +import com.github.manolo8.darkbot.core.entities.Mine; +import com.github.manolo8.darkbot.core.entities.NoCloack; +import com.github.manolo8.darkbot.core.entities.Npc; +import com.github.manolo8.darkbot.core.entities.Pet; +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.entities.Ship; import com.github.manolo8.darkbot.core.itf.Obstacle; import com.github.manolo8.darkbot.core.itf.Updatable; -import com.github.manolo8.darkbot.core.objects.swf.Array; +import com.github.manolo8.darkbot.core.objects.swf.ObjArray; +import com.github.manolo8.darkbot.core.utils.factory.EntityFactory; +import com.github.manolo8.darkbot.core.utils.factory.EntityRegistry; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import static com.github.manolo8.darkbot.Main.API; +import static com.github.manolo8.darkbot.core.utils.factory.EntityFactory.*; public class EntityList extends Updatable { + public final EntityRegistry entityRegistry = new EntityRegistry(); + + public final List obstacles = new ArrayList<>(); + public final List> allEntities = new ArrayList<>(); + + public final List barriers = register(BARRIER); + public final List noCloack = register(MIST_ZONE); + public final List boxes = register(BOX, ORE); + public final List mines = register(MINE); + public final List npcs = register(NPC, LOW_RELAY); + public final List portals = register(PORTAL); + public final List ships = register(PLAYER, PET); + public final List pets = register(PET); + public final List battleStations = register(CBS_ASTEROID, CBS_MODULE, CBS_STATION, CBS_MODULE_CON, CBS_CONSTRUCTION); + public final List basePoints = register(BASE_HANGAR, BASE_STATION, HEADQUARTER, QUEST_GIVER, BASE_TURRET, REPAIR_STATION, REFINERY); + public final List unknown = new ArrayList<>(); + public final FakeNpc fakeNpc; private final Main main; - private final Array entitiesAddress; - private final List> allEntities; - private final HashSet ids; - - public final List obstacles; - - public final List barriers; - public final List boxes; - public final List npcs; - public final List portals; - public final List ships; - public final List battleStations; - public final List unknown; + private final Set ids = new HashSet<>(); + private final ObjArray entitiesArr = ObjArray.ofVector(); public EntityList(Main main) { - this.main = main; - - this.entitiesAddress = new Array(0); - this.allEntities = new ArrayList<>(); - - this.ids = new HashSet<>(); - - this.obstacles = new ArrayList<>(); - - this.barriers = new ArrayList<>(); - this.boxes = new ArrayList<>(); - this.npcs = new ArrayList<>(); - this.portals = new ArrayList<>(); - this.ships = new ArrayList<>(); - this.battleStations = new ArrayList<>(); - this.unknown = new ArrayList<>(); + this.main = main; + this.fakeNpc = new FakeNpc(main); + this.entityRegistry.setMain(main); - this.allEntities.add(barriers); - this.allEntities.add(boxes); - this.allEntities.add(npcs); - this.allEntities.add(portals); - this.allEntities.add(ships); - this.allEntities.add(battleStations); - this.allEntities.add(unknown); + this.entityRegistry.addToAll(entity -> { + if (entity instanceof Obstacle) obstacles.add((Obstacle) entity); + }); + this.entityRegistry.addDefault(unknown::add); this.main.status.add(this::refreshRadius); } @Override public void update() { - synchronized (Main.UPDATE_LOCKER) { removeAllInvalidEntities(); - refreshEntities(); + updatePing(main.mapManager.pingLocation, main.guiManager.pet.getTrackedNpc()); } - } @Override public void update(long address) { super.update(address); - - entitiesAddress.update(API.readMemoryLong(address + 40)); - - clear(); + this.clear(); + this.entitiesArr.update(API.readMemoryLong(address + 40)); } - private void refreshEntities() { - - entitiesAddress.update(); - - for (int i = 0; i < entitiesAddress.size; i++) { - - long found = entitiesAddress.elements[i]; - - int id = API.readMemoryInt(found + 56); - - if (!ids.add(id)) continue; - - int rnd = API.readMemoryInt(found + 112); - - String key = API.readMemoryString(API.readMemoryLong(found + 136)); - - if (!key.equals("ERROR") && !key.isEmpty()) { - System.out.println(key); - } - - if (key.equals("NOA")) { - barriers.add(whenAdd(new Barrier(id), found)); - } else if (id < 0 && rnd == 3) { - boxes.add(whenAdd(new Box(id), found)); - } else if (id <= 150000499 && id >= 150000156) { - portals.add(whenAdd(main.starManager.fromIdPortal(id), found)); - } else if (id <= 150000950 && id >= 150000500) { - - int hullId = API.readMemoryInt(found + 116); - - if (hullId > 0 && hullId < 255) { - battleStations.add(whenAdd(new BattleStation(id), found)); - } else { - unknown.add(whenAdd(new Entity(id), found)); - } - - } else { + @SuppressWarnings("unchecked") + private List register(EntityFactory... types) { + List list = new ArrayList<>(); + this.allEntities.add(list); - int npc = API.readMemoryInt(found + 112); - int visible = API.readMemoryInt(found + 116); - int c = API.readMemoryInt(found + 120); - int d = API.readMemoryInt(found + 124); - - if ((visible == 1 || visible == 0) && (c == 1 || c == 0) && d == 0) { - if (npc == 1) { - npcs.add(whenAdd(new Npc(id), found)); - } else if (npc == 0 && found != main.hero.address && found != main.hero.pet.address) { - ships.add(whenAdd(new Ship(id), found)); - } - } else { - unknown.add(whenAdd(new Entity(id), found)); - } - - } - - } + for (EntityFactory type : types) + this.entityRegistry.add(type, e -> list.add((T) e)); + return list; } - private E whenAdd(E entity, long address) { - - entity.update(address); - entity.update(); - - if (entity instanceof Obstacle) - obstacles.add((Obstacle) entity); - - if (main.isRunning()) - entity.clickable.setRadius(0); - - return entity; - } + private void refreshEntities() { + entitiesArr.update(); + for (int i = 0; i < entitiesArr.getSize(); i++) { + long entityPtr = entitiesArr.get(i); - private void whenRemove(Entity entity) { - entity.removed(); + int id = API.readMemoryInt(entityPtr + 56); + if (ids.add(id)) entityRegistry.sendEntity(id, entityPtr); + } } private void removeAllInvalidEntities() { - for (List entities : allEntities) { - for (int i = 0; i < entities.size(); i++) { - Entity entity = entities.get(i); + main.hero.pet.removed = main.hero.pet.isInvalid(address); - if (entity.isInvalid(address)) { - entities.remove(i); + for (List entities : allEntities) { + // Remove invalid entities and update valid ones + entities.removeIf(entity -> { + if (entity.isInvalid(address) || entity.address == main.hero.address || entity.address == main.hero.pet.address) { ids.remove(entity.id); - whenRemove(entity); - i--; - } else { - entity.update(); - } - } + entity.removed(); + return true; + } + entity.update(); + return false; + }); } - for (int i = 0; i < obstacles.size(); i++) { - Obstacle obstacle = obstacles.get(i); - if (obstacle.isRemoved()) { - obstacles.remove(i); - i--; - } - } + this.obstacles.removeIf(Obstacle::isRemoved); } - private void doInEachEntity(Consumer consumer) { - for (List entities : allEntities) { - entities.forEach(consumer); - } + public void updatePing(Location location, NpcInfo info) { + fakeNpc.set(location, info); + boolean shouldBeNpc = location != null && info != null && fakeNpc.isPingAlive() && + npcs.stream().noneMatch(n -> fakeNpc != n && n.npcInfo == info && n.locationInfo.distance(location) < 500); + + if (!shouldBeNpc) npcs.remove(fakeNpc); + else if (!npcs.contains(fakeNpc)) npcs.add(fakeNpc); } - private void clear() { + private void doInEachEntity(Consumer consumer) { + allEntities.forEach(entities -> entities.forEach(consumer)); + } - ids.clear(); + public void clear() { + synchronized (Main.UPDATE_LOCKER) { + ids.clear(); + entityRegistry.clearCache(); - obstacles.clear(); + obstacles.clear(); + fakeNpc.removed(); - for (List entities : allEntities) { - for (Entity entity : entities) { - entity.removed = true; - } - entities.clear(); + allEntities.forEach(entities -> { + entities.forEach(Entity::removed); + entities.clear(); + }); } } private void refreshRadius(boolean value) { - if (value) doInEachEntity(entity -> entity.clickable.setRadius(0)); - else doInEachEntity(entity -> entity.clickable.reset()); + synchronized (Main.UPDATE_LOCKER) { + if (value) doInEachEntity(entity -> entity.clickable.setRadius(0)); + else doInEachEntity(entity -> entity.clickable.reset()); + } } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/Lazy.java b/src/main/java/com/github/manolo8/darkbot/core/utils/Lazy.java index 50c4e68b..e87e1bcc 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/Lazy.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/Lazy.java @@ -6,36 +6,84 @@ import java.util.function.Consumer; public class Lazy { + protected final List> consumers = new ArrayList<>(); + @Deprecated public C value; - private final List> consumers; + public Lazy() {} + public Lazy(C value) { this.value = value; } - public C value; + /** + * Adds consumer to consumers list if doesn't exists. + * + * @param consumer to add + */ + public void add(Consumer consumer) { + if (!consumers.contains(consumer)) this.consumers.add(consumer); + } - public Lazy() { - this.consumers = new ArrayList<>(); + /** + * @return current stored value + */ + public C get() { + return this.value; } - public Lazy(C value) { - this(); - this.value = value; + /** + * Removes consumer from the consumer list. + * + * @param consumer to remove + */ + @Deprecated + public void remove(Consumer consumer) { + this.consumers.remove(consumer); } - public void add(Consumer consumer) { - this.consumers.add(consumer); + /** + * Removes consumer from the consumer list. + * + * @param consumer to remove + * @return result of {@link List#remove(Object)} + */ + public boolean remove2(Consumer consumer) { + return this.consumers.remove(consumer); } /** * In general, if value is 0, is not loaded or not working! + *

+ * Will execute consumers list with provided value + * if provided value do not equals old value. * - * @param value value + * @param value value to send */ public void send(C value) { - if (!Objects.equals(this.value, value)) { - this.value = value; + if (Objects.equals(get(), value)) return; + this.value = value; + + for (Consumer consumer : consumers) + consumer.accept(value); + } + + public static class Sync extends Lazy { + private C newValue; + + @Override + public void send(C value) { + newValue = value; + } + + public void tick() { + super.send(newValue); + } + } + + public static class NoCache extends Lazy { + @Override + public void send(C value) { for (Consumer consumer : consumers) { consumer.accept(value); } } } -} +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/Location.java b/src/main/java/com/github/manolo8/darkbot/core/utils/Location.java index 3fdd33f5..1cd25f78 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/Location.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/Location.java @@ -1,8 +1,15 @@ package com.github.manolo8.darkbot.core.utils; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.objects.LocationInfo; + +import java.util.Objects; + import static java.lang.Math.pow; import static java.lang.Math.sqrt; import static java.lang.StrictMath.atan2; +import static java.lang.StrictMath.cos; +import static java.lang.StrictMath.sin; public class Location { @@ -17,30 +24,66 @@ public Location(double x, double y) { this.y = y; } + public static Location of(Location loc, double angle, double distance) { + return new Location(loc.x - cos(angle) * distance, loc.y - sin(angle) * distance); + } + public double distance(double ox, double oy) { return sqrt(pow(x - ox, 2) + pow(y - oy, 2)); } public double distance(Location o) { - return sqrt(pow(x - o.x, 2) + pow(y - o.y, 2)); + return distance(o.x, o.y); + } + + public double distance(LocationInfo o) { + return distance(o.now); + } + + public double distance(Entity e) { + return distance(e.locationInfo); } public double angle(Location o) { return atan2(y - o.y, x - o.x); } + public Location toAngle(Location center, double angle, double distance) { + this.x = center.x - cos(angle) * distance; + this.y = center.y - sin(angle) * distance; + return this; + } + + public Location set(double x, double y) { + this.x = x; + this.y = y; + return this; + } + + public Location set(Location o) { + return set(o.x, o.y); + } + public Location copy() { return new Location(x, y); } @Override - public boolean equals(Object obj) { - if (obj instanceof Location) { - Location location = (Location) obj; + public String toString() { + return (int) x + "," + (int) y; + } - return location.x == x && location.y == y; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Location location = (Location) o; + return Double.compare(location.x, x) == 0 && + Double.compare(location.y, y) == 0; + } - return false; + @Override + public int hashCode() { + return Objects.hash(x, y); } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/factory/EntityFactory.java b/src/main/java/com/github/manolo8/darkbot/core/utils/factory/EntityFactory.java new file mode 100644 index 00000000..824ed984 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/factory/EntityFactory.java @@ -0,0 +1,179 @@ +package com.github.manolo8.darkbot.core.utils.factory; + +import com.github.manolo8.darkbot.core.entities.Barrier; +import com.github.manolo8.darkbot.core.entities.BasePoint; +import com.github.manolo8.darkbot.core.entities.BattleStation; +import com.github.manolo8.darkbot.core.entities.Box; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.entities.MapNpc; +import com.github.manolo8.darkbot.core.entities.Mine; +import com.github.manolo8.darkbot.core.entities.NoCloack; +import com.github.manolo8.darkbot.core.entities.Npc; +import com.github.manolo8.darkbot.core.entities.Pet; +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.entities.Ship; +import com.github.manolo8.darkbot.core.entities.bases.BaseStation; +import com.github.manolo8.darkbot.core.entities.bases.BaseTurret; +import com.github.manolo8.darkbot.core.entities.bases.BaseHangar; +import com.github.manolo8.darkbot.core.entities.bases.BaseHeadquarters; +import com.github.manolo8.darkbot.core.entities.bases.QuestGiver; +import com.github.manolo8.darkbot.core.entities.bases.BaseRefinery; +import com.github.manolo8.darkbot.core.entities.bases.BaseRepairStation; +import com.github.manolo8.darkbot.core.manager.StarManager; +import com.github.manolo8.darkbot.core.utils.ByteUtils; +import org.intellij.lang.annotations.Language; + +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.regex.Pattern; + +import static com.github.manolo8.darkbot.Main.API; + +public enum EntityFactory { + BOX (Box::new, "box_.*"), + ORE (Box::new, "ore_.*"), + X2_BEACON (Box::new, "beacon_.*"), + MINE (Mine::new, "mine_.*"), + FIREWORK (Entity::new, "firework_box"), + + LOW_RELAY (MapNpc::new, "relay"), + NPC_BEACON (MapNpc::new, "npc-beacon.*"), + SPACE_BALL (MapNpc::new, "mapIcon_spaceball"), + + CBS_ASTEROID (BattleStation::new, "asteroid"), + CBS_CONSTRUCTION(BattleStation::new, "cbs-construction"), + CBS_MODULE (BattleStation::new, "wreck|module_.*"), // addr+112 moduleType string + CBS_MODULE_CON (BattleStation::new, "module-construction"), + CBS_STATION (BattleStation::new, "battleStation"), + + POD_HEAL (Entity::new, "pod_heal"), // Aegis/hammerclaw healing pods + BUFF_CAPSULE (Entity::new, "buffCapsule_.*"), + BURNING_TRAIL (Entity::new, "burning_trail_entity_.*"), + PLUTUS_GENERATOR(Entity::new, "plutus-generator"), + + BASE_TURRET (BaseTurret::new, "turret_.*"), // Turrets around x-1 and x-8 bases + REFINERY (BaseRefinery::new, "refinery_.*"), // Refinery to sell ores at + BASE_HANGAR (BaseHangar::new, "hangar_.*"), // Hangar inside bases + HEADQUARTER (BaseHeadquarters::new, "headquarters_.*"), // Headquarters in middle of x-1 and x-8 + REPAIR_STATION (BaseRepairStation::new,"repairstation_.*"),// Repair station inside bases + QUEST_GIVER (QuestGiver::new, "questgiver_.*"), // Quest givers on x-1, x-4, x-5 and x-8 + BASE_STATION (BaseStation::new, "station_.*"), // Standalone station on 5-2 + CTB_HOME_ZONE (BasePoint::new, "ctbHomeZone_.*"), + + PORTAL (EntityFactory::getOrCreatePortal, "[0-9]+$"), + + ZONE (EntityFactory::isZone, EntityFactory::defineZoneType), // Generic zone, redirects to BARRIER or MIST_ZONE + BARRIER (Barrier::new), + MIST_ZONE(NoCloack::new), + + PET (Pet::new, EntityFactory::isPet), + SHIP (EntityFactory::isShip, EntityFactory::defineShipType), // Generic ship, redirects to PLAYER or NPC + PLAYER (Ship::new), + NPC (Npc::new), + + UNKNOWN (Entity::new, (asset, addr) -> true); + + // Constructor to create the entity of this type + private final BiFunction constructor; + // Test if the type matches for the asset & address provided + private final BiPredicate typeMatcher; + // Will mutate the type to a different (specialized) type in the bot + private final Function typeModifier; + + + EntityFactory(BiFunction constructor) { + this(constructor, (asset, addr) -> false, null); + } + + EntityFactory(BiFunction constructor, @Language("RegExp") String regex) { + this(constructor, regexMatcher(regex)); + } + + EntityFactory(BiFunction constructor, BiPredicate typeMatcher) { + this(constructor, typeMatcher, null); + } + + EntityFactory(BiPredicate typeMatcher, Function typeModifier) { + this(null, typeMatcher, typeModifier); + } + + EntityFactory(BiFunction constructor, BiPredicate typeMatcher, Function typeModifier) { + this.constructor = constructor; + this.typeModifier = typeModifier; + this.typeMatcher = typeMatcher; + } + + private static BiPredicate regexMatcher(String regex) { + Pattern p = Pattern.compile(regex); + return (asset, addr) -> p.matcher(asset).matches(); + } + + public EntityFactory get(long address) { + return this.typeModifier == null ? this : this.typeModifier.apply(address); + } + + public Entity createEntity(int id, long address) { + return this.constructor == null ? null : this.constructor.apply(id, address); + } + + public static EntityFactory find(long address) { + String assetId = getAssetId(address); + + for (EntityFactory type : EntityFactory.values()) { + if (type.typeMatcher.test(assetId, address)) return type; + } + + return UNKNOWN; + } + + private static String getAssetId(long address) { + long temp = API.readMemoryLong(address, 48, 48, 16) & ByteUtils.FIX; + return API.readMemoryString(temp, 64, 32, 24, 8, 16, 24).trim(); + } + + private static String getZoneKey(long address) { + return API.readMemoryString(address, 136).trim(); + } + + private static boolean isZone(String asset, long address) { + String key = getZoneKey(address); + return key.equals("NOA") || key.equals("DMG") || key.equals("TRG"); + } + + private static boolean isPet(String asset, long address) { + return API.readMemoryString(address, 192, 136).trim().equals("pet"); + } + + private static boolean isShip(String asset, long address) { + int id = API.readMemoryInt(address + 56); + int isNpc = API.readMemoryInt(address + 112); + int visible = API.readMemoryInt(address + 116); + int c = API.readMemoryInt(address + 120); + int d = API.readMemoryInt(address + 124); + + return id > 0 && (isNpc == 1 || isNpc == 0) && + (visible == 1 || visible == 0) && (c == 1 || c == 0) && d == 0; + } + + private static EntityFactory defineZoneType(long address) { + String key = getZoneKey(address); + return key.equals("NOA") ? BARRIER : key.equals("DMG") ? MIST_ZONE : UNKNOWN; + } + + private static EntityFactory defineShipType(long address) { + int isNpc = API.readMemoryInt(address + 112); + return isNpc == 1 ? NPC : isNpc == 0 ? PLAYER : UNKNOWN; + } + + private static Portal getOrCreatePortal(int id, long address) { + int portalType = API.readMemoryInt(address + 112); + int x = (int) API.readMemoryDouble(address, 64, 32); + int y = (int) API.readMemoryDouble(address, 64, 40); + + Portal portal = StarManager.getInstance().getOrCreate(id, portalType, x, y); + portal.update(address); + + return portal; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/factory/EntityRegistry.java b/src/main/java/com/github/manolo8/darkbot/core/utils/factory/EntityRegistry.java new file mode 100644 index 00000000..67a53b18 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/factory/EntityRegistry.java @@ -0,0 +1,60 @@ +package com.github.manolo8.darkbot.core.utils.factory; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.utils.Lazy; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static com.github.manolo8.darkbot.Main.API; + +public class EntityRegistry { + private final Map cachedTypes = new HashMap<>(); + private final Map> listeners = new EnumMap<>(EntityFactory.class); + private final Lazy fallback = new Lazy.NoCache<>(); + private Main main; + + public void setMain(Main main) { + this.main = main; + } + + public void add(EntityFactory type, Consumer consumer) { + listeners.computeIfAbsent(type, k -> new Lazy.NoCache<>()).add(consumer); + } + + public void addDefault(Consumer consumer) { + fallback.add(consumer); + } + + public void addToAll(Consumer consumer) { + listeners.forEach((f, e) -> add(f, consumer)); + } + + public boolean remove(EntityFactory type, Consumer consumer) { + Lazy l = listeners.get(type); + return l != null && l.remove2(consumer); + } + + public void clearCache() { + cachedTypes.clear(); + } + + public void sendEntity(int id, long address) { + EntityFactory type = cachedTypes.computeIfAbsent(API.readMemoryLong(address + 0x10), + l -> EntityFactory.find(address)).get(address); + if (address == main.hero.address || address == main.hero.pet.address) return; + + Entity entity = type.createEntity(id, address); + if (entity == null) return; + entity.added(main); + entity.update(); + + if (main.isRunning()) entity.clickable.setRadius(0); + + listeners.getOrDefault(type, fallback).send(entity); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/Area.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/Area.java index e4c08c45..89e18c09 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/Area.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/Area.java @@ -6,20 +6,22 @@ public class Area { - public double maxX; public double minX; - public double maxY; public double minY; + public double maxX; + public double maxY; public boolean changed; public boolean cachedUsing; - public Area(double maxX, double minX, double maxY, double minY) { - this.maxX = maxX; - this.minX = minX; - this.maxY = maxY; - this.minY = minY; - this.changed = true; + public Area(double minX, double minY, double maxX, double maxY) { + this.minX = Math.floor(minX); + this.minY = Math.floor(minY); + this.maxX = Math.ceil(maxX); + this.maxY = Math.ceil(maxY); + } + public static Area ofSize(double minX, double minY, double width, double height) { + return new Area(minX, minY, minX + width, minY + height); } public boolean hasLineOfSight(PathPoint current, PathPoint destination) { @@ -36,7 +38,7 @@ else if (y1 < y2 && lineCollisionLocation(x1, y1, x2, y2, minX, minY, maxX, minY else return y1 > y2 && lineCollisionLocation(x1, y1, x2, y2, minX, maxY, maxX, maxY); } - public void toSide(PathPoint point) { + public PathPoint toSide(PathPoint point) { int diffLeft = point.x - (int) minX; int diffRight = (int) maxX - point.x; @@ -47,13 +49,14 @@ public void toSide(PathPoint point) { int min = min(diffBottom, min(min(diffTop, diffLeft), diffRight)); if (min == diffTop) - point.y = (int) minY - 1; + point.y = (int) minY - 5; else if (min == diffBottom) - point.y = (int) maxY + 1; + point.y = (int) maxY + 5; else if (min == diffLeft) - point.x = (int) minX - 1; + point.x = (int) minX - 5; else - point.x = (int) maxX + 1; + point.x = (int) maxX + 5; + return point; } private boolean lineCollisionLocation(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) { @@ -65,10 +68,8 @@ private boolean lineCollisionLocation(double x1, double y1, double x2, double y2 } public boolean inside(int x, int y) { - return (minX <= x && - x <= maxX && - minY <= y && - y <= maxY); + return (minX <= x && x <= maxX && + minY <= y && y <= maxY); } public void set(Location o, int addX, int addY) { @@ -80,4 +81,18 @@ public void set(Location o, int addX, int addY) { changed = true; } + public void set(double minX, double minY, double maxX, double maxY) { + if (this.minX == minX && this.minY == minY && this.maxX == maxX && this.maxY == maxY) return; + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + + changed = true; + } + + public double size() { + return (maxX - minX) * (maxY - minY); + } + } diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/FixedTwoOptHeuristicTSP.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/FixedTwoOptHeuristicTSP.java new file mode 100644 index 00000000..221e364e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/FixedTwoOptHeuristicTSP.java @@ -0,0 +1,321 @@ +package com.github.manolo8.darkbot.core.utils.pathfinder; + +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.GraphTests; +import org.jgrapht.Graphs; +import org.jgrapht.alg.interfaces.HamiltonianCycleAlgorithm; +import org.jgrapht.graph.GraphWalk; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; + +/** + * The 2-opt heuristic algorithm for the TSP problem. + * + *

+ * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and + * the distances between each pair of cities, what is the shortest possible route that visits each + * city exactly once and returns to the origin city?". + * + *

+ * This is an implementation of the 2-opt improvement heuristic algorithm. The algorithm generates k + * random initial tours and then iteratively improves the tours until a local minimum is reached. In + * each iteration it applies the best possible 2-opt move which means to find the best pair of edges + * $(i,i+1)$ and $(j,j+1)$ such that replacing them with $(i,j)$ and $(i+1,j+1)$ minimizes the tour + * length. + * + *

+ * See wikipedia for more details. + * + *

+ * This implementation can also be used in order to try to improve an existing tour. See method + * {@link #improveTour(GraphPath)}. + * + * @param the graph vertex type + * @param the graph edge type + * + * @author Dimitrios Michail + */ +public class FixedTwoOptHeuristicTSP + implements + HamiltonianCycleAlgorithm +{ + private int k; + private Random rng; + + private Graph graph; + private int n; + private double[][] dist; + private Map index; + private Map revIndex; + + /** + * Constructor. By default one initial random tour is used. + */ + public FixedTwoOptHeuristicTSP() + { + this(1, new Random()); + } + + /** + * Constructor + * + * @param k how many initial random tours to check + */ + public FixedTwoOptHeuristicTSP(int k) + { + this(k, new Random()); + } + + /** + * Constructor + * + * @param k how many initial random tours to check + * @param seed seed for the random number generator + */ + public FixedTwoOptHeuristicTSP(int k, long seed) + { + this(k, new Random(seed)); + } + + /** + * Constructor + * + * @param k how many initial random tours to check + * @param rng random number generator + */ + public FixedTwoOptHeuristicTSP(int k, Random rng) + { + if (k < 1) { + throw new IllegalArgumentException("k must be at least one"); + } + this.k = k; + this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); + } + + /** + * Computes a 2-approximate tour. + * + * @param graph the input graph + * @return a tour + * @throws IllegalArgumentException if the graph is not undirected + * @throws IllegalArgumentException if the graph is not complete + * @throws IllegalArgumentException if the graph contains no vertices + */ + @Override + public GraphPath getTour(Graph graph) + { + /* + * Initialize vertex index and distances + */ + init(graph); + + /* + * Special case singleton vertex + */ + if (graph.vertexSet().size() == 1) { + V start = graph.vertexSet().iterator().next(); + return new GraphWalk<>( + graph, start, start, Collections.singletonList(start), Collections.emptyList(), 0d); + } + + /* + * Execute 2-opt from k random permutations + */ + GraphPath best = tourToPath(improve(createRandomTour())); + for (int i = 1; i < k; i++) { + GraphPath other = tourToPath(improve(createRandomTour())); + if (other.getWeight() < best.getWeight()) { + best = other; + } + } + return best; + } + + /** + * Try to improve a tour by running the 2-opt heuristic. + * + * @param tour a tour + * @return a possibly improved tour + */ + public GraphPath improveTour(GraphPath tour) + { + init(tour.getGraph()); + return tourToPath(improve(pathToTour(tour))); + } + + /** + * Initialize graph and mapping to integer vertices. + * + * @param graph the input graph + */ + private void init(Graph graph) + { + this.graph = GraphTests.requireUndirected(graph); + + if (!GraphTests.isComplete(graph)) { + throw new IllegalArgumentException("Graph is not complete"); + } + if (graph.vertexSet().isEmpty()) { + throw new IllegalArgumentException("Graph contains no vertices"); + } + + this.n = graph.vertexSet().size(); + this.dist = new double[n][n]; + this.index = new HashMap<>(); + this.revIndex = new HashMap<>(); + int i = 0; + for (V v : graph.vertexSet()) { + index.put(v, i); + revIndex.put(i, v); + i++; + } + + for (E e : graph.edgeSet()) { + V s = graph.getEdgeSource(e); + int si = index.get(s); + V t = graph.getEdgeTarget(e); + int ti = index.get(t); + double weight = graph.getEdgeWeight(e); + dist[si][ti] = weight; + dist[ti][si] = weight; + } + } + + /** + * Create a random tour + * + * @return a random tour + */ + private int[] createRandomTour() + { + int[] tour = new int[n + 1]; + for (int i = 0; i < n; i++) { + tour[i] = i; + } + for (int i = n; i > 1; i--) { + int j = rng.nextInt(i); + int tmp = tour[i - 1]; + tour[i - 1] = tour[j]; + tour[j] = tmp; + } + tour[n] = tour[0]; + return tour; + } + + /** + * Improve the tour using the 2-opt heuristic. In each iteration it applies the best possible + * 2-opt move which means to find the best pair of edges $(i,i+1)$ and $(j,j+1)$ such that + * replacing them with $(i,j)$ and $(i+1,j+1)$ minimizes the tour length. + * + *

+ * The returned array instance might or might not be the input array. + * + * @param tour the input tour + * @return a possibly improved tour + */ + private int[] improve(int[] tour) + { + int[] newTour = new int[n + 1]; + double minChange; + do { + minChange = 0d; + int mini = -1; + int minj = -1; + for (int i = 0; i < n - 2; i++) { + for (int j = i + 2; j < n; j++) { + int ci = tour[i]; + int ci1 = tour[i + 1]; + int cj = tour[j]; + int cj1 = tour[j + 1]; + double change = dist[ci][cj] + dist[ci1][cj1] - dist[ci][ci1] - dist[cj][cj1]; + if (change < minChange) { + minChange = change; + mini = i; + minj = j; + } + } + } + if (mini != -1 && minj != -1) { + // apply move + int a = 0; + for (int k = 0; k <= mini; k++) { + newTour[a++] = tour[k]; + } + for (int k = minj; k >= mini + 1; k--) { + newTour[a++] = tour[k]; + } + for (int k = minj + 1; k < n + 1; k++) { + newTour[a++] = tour[k]; + } + // swap tours + int[] tmp = tour; + tour = newTour; + newTour = tmp; + } + } while (minChange < -0.0000001d); + + return tour; + } + + /** + * Transform from an array representation to a graph path. + * + * @param tour an array containing the index of the vertices of the tour + * @return a graph path + */ + private GraphPath tourToPath(int[] tour) + { + List tourEdges = new ArrayList(n); + List tourVertices = new ArrayList<>(n + 1); + double tourWeight = 0d; + + V start = revIndex.get(tour[0]); + tourVertices.add(start); + for (int i = 1; i < n + 1; i++) { + V u = revIndex.get(tour[i - 1]); + V v = revIndex.get(tour[i]); + tourVertices.add(v); + E e = graph.getEdge(u, v); + tourEdges.add(e); + tourWeight += graph.getEdgeWeight(e); + } + + return new GraphWalk<>(graph, start, start, tourVertices, tourEdges, tourWeight); + } + + /** + * Transform from a path representation to an array representation. + * + * @param path graph path + * @return an array containing the index of the vertices of the tour + */ + private int[] pathToTour(GraphPath path) + { + Set visited = new HashSet<>(); + int i = 0; + int[] tour = new int[n + 1]; + V v = path.getStartVertex(); + tour[i++] = index.get(v); + for (E e : path.getEdgeList()) { + v = Graphs.getOppositeVertex(graph, e, v); + if (!visited.add(v)) { + throw new IllegalArgumentException("Not a valid tour"); + } + tour[i++] = index.get(v); + } + if (i < n + 1) { + throw new IllegalArgumentException("Not a valid tour"); + } + return tour; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/ObstacleHandler.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/ObstacleHandler.java new file mode 100644 index 00000000..5a13fe07 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/ObstacleHandler.java @@ -0,0 +1,61 @@ +package com.github.manolo8.darkbot.core.utils.pathfinder; + +import com.github.manolo8.darkbot.config.ZoneInfo; +import com.github.manolo8.darkbot.core.itf.Obstacle; +import com.github.manolo8.darkbot.core.manager.MapManager; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ObstacleHandler implements Iterable { + + private MapManager map; + private final List obstacles; + private ZoneInfo avoided; + private int obstacleCount; + private List areas = new ArrayList<>(); + + public ObstacleHandler(MapManager map) { + this.map = map; + this.obstacles = map.entities.obstacles; + this.avoided = new ZoneInfo(1); + } + + boolean changed() { + boolean changed = obstacles.size() != obstacleCount || map.avoided != avoided || avoided.changed || + obstacles.stream().anyMatch(o -> o.use() != o.getArea().cachedUsing || o.getArea().changed); + if (changed) rebuildAreas(); + return changed; + } + + public List get() { + return areas; + } + + public Stream stream() { + return areas.stream(); + } + + private void rebuildAreas() { + if (obstacles == null || map.avoided == null) return; + obstacleCount = obstacles.size(); + avoided = map.avoided; + + double cellSize = 1d / avoided.resolution, + width = cellSize * MapManager.internalWidth, + height = cellSize * MapManager.internalHeight; + + areas = Stream.concat(obstacles.stream().peek(o -> o.getArea().changed = false) + .filter(o -> o.getArea().cachedUsing = o.use()).map(Obstacle::getArea), + avoided.getZones().stream().map(zi -> Area.ofSize(zi.x * width, zi.y * height, width, height))) + .collect(Collectors.toList()); + } + + @Override + public Iterator iterator() { + return areas.iterator(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinder.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinder.java index dfdad12c..3d984cd6 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinder.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinder.java @@ -1,96 +1,79 @@ package com.github.manolo8.darkbot.core.utils.pathfinder; -import com.github.manolo8.darkbot.core.itf.Obstacle; +import com.github.manolo8.darkbot.core.manager.MapManager; import com.github.manolo8.darkbot.core.utils.Location; import java.util.HashSet; import java.util.LinkedList; -import java.util.List; import java.util.Set; -public class PathFinder { +import static java.lang.Math.cos; +import static java.lang.Math.sin; - final LinkedList paths; - final Set points; +public class PathFinder { - final List obstacles; + private final MapManager map; + public final Set points; - private int lastSize; + private final ObstacleHandler obstacleHandler; - public PathFinder(List obstacles) { - this.paths = new LinkedList<>(); - this.obstacles = obstacles; + public PathFinder(MapManager map) { + this.map = map; + this.obstacleHandler = new ObstacleHandler(map); this.points = new HashSet<>(); } - public Location current() { - if (paths.size() > 0) { - PathPoint point = paths.getFirst(); - - return new Location(point.x, point.y); - } - return null; - } - - public void currentCompleted() { - paths.removeFirst(); - } - - public boolean isEmpty() { - return paths.isEmpty(); - } - - public void createRote(Location current, Location destination) { - createRote( + public LinkedList createRote(Location current, Location destination) { + return createRote( new PathPoint((int) current.x, (int) current.y), new PathPoint((int) destination.x, (int) destination.y) ); } - private void createRote(PathPoint current, PathPoint destination) { - if (hasLineOfSight(current, destination)) { + public LinkedList createRote(PathPoint current, PathPoint destination) { + LinkedList paths = new LinkedList<>(); + fixToClosest(current); + fixToClosest(destination); - paths.clear(); + if (hasLineOfSight(current, destination)) { paths.add(destination); + return paths; + } - } else { - - paths.clear(); - - checkModification(); - - fixToClosest(current); - fixToClosest(destination); - - current.fillLineOfSight(this); - destination.fillLineOfSight(this); + current.fillLineOfSight(this); + destination.fillLineOfSight(this); - new PathFinderCalculator( - current, - destination - ).fillGeneratedPathTo(paths); - } + new PathFinderCalculator(current, destination) + .fillGeneratedPathTo(paths); + return paths; } - //Need some improvements '-' - private void fixToClosest(PathPoint point) { + public PathPoint fixToClosest(PathPoint point) { + int initialX = point.x, initialY = point.y; Area area = areaTo(point); - - if (area != null) { - area.toSide(point); - } - - area = areaTo(point); - - //Well, our method fails... - if (area != null) { + if (area != null) area = areaTo(area.toSide(point)); // Inside an area, get out of it + if (map.isOutOfMap(point.x, point.y)) { // In radiation, get out of it + point.x = Math.min(Math.max(point.x, 0), MapManager.internalWidth); + point.y = Math.min(Math.max(point.y, 0), MapManager.internalHeight); + if (areaTo(point) == null) return point; // Got out of rad and not in area + } else if (area == null) return point; // Inside map & not in area (anymore) + + double angle = 0, distance = 0; + do { + point.x = initialX - (int) (cos(angle) * distance); + point.y = initialY - (int) (sin(angle) * distance); + angle += 0.3; + distance += 2; + } while (areaTo(point) != null || map.isOutOfMap(point.x, point.y) && distance < 20000); + + if (distance >= 20000) { PathPoint closest = closest(point); - + if (closest == null) return point; point.x = closest.x; point.y = closest.y; } - + return point; } private PathPoint closest(PathPoint point) { @@ -111,124 +94,56 @@ private PathPoint closest(PathPoint point) { return current; } - private void checkModification() { - for (Obstacle obstacle : obstacles) { - - Area area = obstacle.getArea(); - - if (area.changed || area.cachedUsing != obstacle.use()) { - rebuild(); - return; - } - } - - if (obstacles.size() != lastSize) { - rebuild(); - } - } - - private void rebuild() { + public boolean changed() { + if (!obstacleHandler.changed()) return false; points.clear(); - lastSize = obstacles.size(); rebuildPoints(); rebuildLineOfSight(); + return true; } private void rebuildPoints() { - for (Obstacle obstacle : obstacles) { - - if (obstacle.use()) { - - Area a = obstacle.getArea(); - - a.changed = false; - - //LEFT AND TOP - checkAndAddPoint(new PathPoint((int) a.minX - 1, (int) a.minY - 1)); - //LEFT AND BOTTOM - checkAndAddPoint(new PathPoint((int) a.minX - 1, (int) (a.maxY) + 1)); - //RIGHT AND TOP - checkAndAddPoint(new PathPoint((int) (a.maxX) + 1, (int) a.minY - 1)); - //RIGHT AND BOTTOM - checkAndAddPoint(new PathPoint((int) (a.maxX) + 1, (int) (a.maxY) + 1)); - } - + for (Area a : obstacleHandler) { + checkAndAddPoint((int) a.minX, (int) a.minY, Corner.TOP_LEFT); + checkAndAddPoint((int) a.maxX, (int) a.minY, Corner.TOP_RIGHT); + checkAndAddPoint((int) a.minX, (int) a.maxY, Corner.BOTTOM_LEFT); + checkAndAddPoint((int) a.maxX, (int) a.maxY, Corner.BOTTOM_RIGHT); } } - private void checkAndAddPoint(PathPoint point) { - if (collisionCount(point) == 0) { - points.add(point); - } + private void checkAndAddPoint(int x, int y, Corner corner) { + if (!map.isOutOfMap(x, y) + && canMove(x + corner.x, y - corner.y) + && canMove(x - corner.x, y + corner.y) + && canMove(x + corner.x, y + corner.y)) + points.add(new PathPoint(x + corner.x, y + corner.y)); } - private int collisionCount(PathPoint point) { - - int count = 0; - - for (Obstacle obstacle : obstacles) { - if (obstacle.use()) { - Area area = obstacle.getArea(); - if (area.inside(point.x, point.y)) - count++; - } - } - - return count; + public boolean canMove(int x, int y) { + return obstacleHandler.stream().noneMatch(a -> a.inside(x, y)); } private Area areaTo(PathPoint point) { - for (Obstacle obstacle : obstacles) { - if (obstacle.use()) { - Area area = obstacle.getArea(); - if (area.inside(point.x, point.y)) { - return area; - } - } - } - - return null; - } - - public boolean canMove(Location location) { - for (Obstacle obstacle : obstacles) { - if (obstacle.use()) { - Area area = obstacle.getArea(); - if (area.inside((int) location.x, (int) location.y)) { - return false; - } - } - } - - return true; + return obstacleHandler.stream().filter(a -> a.inside(point.x, point.y)).findAny().orElse(null); } private void rebuildLineOfSight() { - for (PathPoint point : points) { - point.fillLineOfSight(this); - } + for (PathPoint point : points) point.fillLineOfSight(this); } boolean hasLineOfSight(PathPoint point1, PathPoint point2) { + return obstacleHandler.stream().allMatch(a -> a.hasLineOfSight(point1, point2)); + } - for (Obstacle obstacle : obstacles) { - - if (obstacle.use()) { - - Area area = obstacle.getArea(); - - if (!area.hasLineOfSight(point1, point2)) { - return false; - } - } + private enum Corner { + TOP_LEFT(-1, -1), TOP_RIGHT(1, -1), BOTTOM_LEFT(-1, 1),BOTTOM_RIGHT(1, 1); + int x,y; + Corner(int x, int y) { + this.x = x; + this.y = y; } - - return true; } - public List path() { - return paths; - } } diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinderCalculator.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinderCalculator.java index 9a6a8a6f..da30bf50 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinderCalculator.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathFinderCalculator.java @@ -1,6 +1,10 @@ package com.github.manolo8.darkbot.core.utils.pathfinder; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; public class PathFinderCalculator { @@ -27,7 +31,7 @@ public void fillGeneratedPathTo(LinkedList target) { addDefs(); - build(); + if (!build()) return; unfragment(target); @@ -50,7 +54,7 @@ private void remDefs() { temp.lineOfSight.remove(come); } - private void build() { + private boolean build() { PathPoint current = come; @@ -68,8 +72,8 @@ private void build() { closedList.add(current); update(current); - } - while ((current = pickupOne()) != destination); + } while ((current = pickupOne()) != destination && !openList.isEmpty()); + return current == destination; } private void update(PathPoint current) { diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathPoint.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathPoint.java index c386b516..a95bd6d7 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathPoint.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathfinder/PathPoint.java @@ -1,5 +1,7 @@ package com.github.manolo8.darkbot.core.utils.pathfinder; +import com.github.manolo8.darkbot.core.utils.Location; + import java.util.HashSet; import static java.lang.Math.pow; @@ -35,6 +37,10 @@ public void fillLineOfSight(PathFinder finder) { lineOfSight.add(point); } + public Location toLocation() { + return new Location(x, y); + } + @Override public int hashCode() { int var1 = 1664525 * this.x + 1013904223; diff --git a/src/main/java/com/github/manolo8/darkbot/core/utils/pathscore/PathScore.java b/src/main/java/com/github/manolo8/darkbot/core/utils/pathscore/PathScore.java index 406050d3..19225998 100644 --- a/src/main/java/com/github/manolo8/darkbot/core/utils/pathscore/PathScore.java +++ b/src/main/java/com/github/manolo8/darkbot/core/utils/pathscore/PathScore.java @@ -4,8 +4,8 @@ //import com.github.manolo8.darkbot.core.entities.Npc; //import com.github.manolo8.darkbot.core.manager.MapManager; // -//import java.util.List; -//import java.util.Random; +//import java.utils.List; +//import java.utils.Random; // //public class PathScore { // diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/Feature.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/Feature.java new file mode 100644 index 00000000..fbc87112 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/Feature.java @@ -0,0 +1,14 @@ +package com.github.manolo8.darkbot.extensions.features; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Feature { + String name(); + String description(); + boolean enabledByDefault() default false; +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureDefinition.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureDefinition.java new file mode 100644 index 00000000..d83675bc --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureDefinition.java @@ -0,0 +1,106 @@ +package com.github.manolo8.darkbot.extensions.features; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.core.itf.Module; +import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.extensions.plugins.Plugin; + +import java.util.function.Consumer; + +public class FeatureDefinition { + + private final Plugin plugin; + private final Class clazz; + private final Feature feature; + private final IssueHandler issues; + + private final String id; + private final String name; + private final String description; + + private final Lazy> listener = new Lazy.NoCache<>(); + + private T instance; + + public FeatureDefinition(Plugin plugin, Class clazz) { + this.plugin = plugin; + this.clazz = clazz; + this.feature = clazz.getAnnotation(Feature.class); + this.issues = new IssueHandler(plugin == null ? null : plugin.getIssues()); + + this.id = clazz.getCanonicalName(); + this.name = feature.name(); + this.description = feature.description(); + + if (plugin != null + && !plugin.getInfo().ENABLED_FEATURES.contains(id) + && !plugin.getInfo().DISABLED_FEATURES.contains(id)) { + setStatusInternal(Module.class.isAssignableFrom(clazz) || feature.enabledByDefault()); + } + } + + public Plugin getPlugin() { + return plugin; + } + + public Class getClazz() { + return clazz; + } + + public Feature getFeature() { + return feature; + } + + public IssueHandler getIssues() { + return issues; + } + + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public T getInstance() { + return instance; + } + + public void setInstance(T instance) { + this.instance = instance; + listener.send(this); + } + + public void setStatus(boolean enabled) { + if (setStatusInternal(enabled)) { + ConfigEntity.changed(); + listener.send(this); + } + } + + + private boolean setStatusInternal(boolean enabled) { + if (enabled) return plugin.getInfo().ENABLED_FEATURES.add(id) | plugin.getInfo().DISABLED_FEATURES.remove(id); + else return plugin.getInfo().ENABLED_FEATURES.remove(id) | plugin.getInfo().DISABLED_FEATURES.add(id); + } + + public void addStatusListener(Consumer> listener) { + this.listener.add(listener); + } + + public boolean isEnabled() { + return plugin == null || plugin.getInfo().ENABLED_FEATURES.contains(id); + } + + public boolean canLoad() { + return issues.canLoad() && isEnabled(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureInstanceLoader.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureInstanceLoader.java new file mode 100644 index 00000000..c350c7c7 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureInstanceLoader.java @@ -0,0 +1,49 @@ +package com.github.manolo8.darkbot.extensions.features; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Configurable; +import com.github.manolo8.darkbot.core.itf.Installable; +import com.github.manolo8.darkbot.extensions.features.decorators.ConfigurableDecorator; +import com.github.manolo8.darkbot.extensions.features.decorators.FeatureDecorator; +import com.github.manolo8.darkbot.extensions.features.decorators.InstallableDecorator; +import com.github.manolo8.darkbot.extensions.features.decorators.InstructionProviderDecorator; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import java.util.Arrays; +import java.util.List; + +/** + * Takes care of creating instances of features, and calling relevant setup or tear down methods. + * e.g. calling {@link Installable#install(Main main)} or {@link Configurable#setConfig(Object config)} + */ +class FeatureInstanceLoader { + + private final List> FEATURE_DECORATORS; + private final ConfigurableDecorator CONFIGURATION_DECORATOR; + + FeatureInstanceLoader(Main main) { + FEATURE_DECORATORS = Arrays.asList( + new InstallableDecorator(main), + CONFIGURATION_DECORATOR = new ConfigurableDecorator(main), + new InstructionProviderDecorator()); + } + + T loadFeature(FeatureDefinition featureDefinition) { + T feature = ReflectionUtils.createInstance(featureDefinition.getClazz()); + for (FeatureDecorator decorator : FEATURE_DECORATORS) { + decorator.tryLoad(featureDefinition, feature); + } + return feature; + } + + void unloadFeature(T feature) { + for (FeatureDecorator decorator : FEATURE_DECORATORS) { + decorator.tryUnload(feature); + } + } + + void updateConfig(FeatureDefinition fd) { + if (fd.getInstance() != null) CONFIGURATION_DECORATOR.tryLoad(fd, fd.getInstance()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureRegisterHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureRegisterHandler.java new file mode 100644 index 00000000..2e074843 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureRegisterHandler.java @@ -0,0 +1,50 @@ +package com.github.manolo8.darkbot.extensions.features; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.extensions.features.handlers.BehaviourHandler; +import com.github.manolo8.darkbot.extensions.features.handlers.ExtraMenuHandler; +import com.github.manolo8.darkbot.extensions.features.handlers.FeatureHandler; +import com.github.manolo8.darkbot.extensions.features.handlers.ModuleHandler; +import com.github.manolo8.darkbot.extensions.features.handlers.NpcExtraHandler; +import com.github.manolo8.darkbot.extensions.features.handlers.TaskHandler; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +/** + * Takes care of registering features to the bot. + * Includes both taking care of providing native features in the bot, and being able to + * apply actions when the features are going to be reloaded or after they have been loaded. + * e.g. Updating the list of available modules in the dropdown selector in the config. + */ +public class FeatureRegisterHandler { + + private final FeatureRegistry featureRegistry; + private final List> FEATURE_HANDLERS; + + + public FeatureRegisterHandler(Main main, FeatureRegistry featureRegistry) { + this.featureRegistry = featureRegistry; + this.FEATURE_HANDLERS = Arrays.asList( + new ModuleHandler(), + new BehaviourHandler(main, featureRegistry), + new TaskHandler(main, featureRegistry), + new NpcExtraHandler(featureRegistry), + new ExtraMenuHandler(featureRegistry) + ); + } + + Stream> getNativeFeatures() { + return FEATURE_HANDLERS.stream().flatMap(fr -> Arrays.stream(fr.getNativeFeatures())); + } + + void update() { + FEATURE_HANDLERS.forEach(this::update); + } + + private void update(FeatureHandler registerer) { + registerer.update(featureRegistry.getFeatures(registerer.getHandledType())); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureRegistry.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureRegistry.java new file mode 100644 index 00000000..460aa757 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/FeatureRegistry.java @@ -0,0 +1,130 @@ +package com.github.manolo8.darkbot.extensions.features; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.extensions.plugins.Plugin; +import com.github.manolo8.darkbot.extensions.plugins.PluginHandler; +import com.github.manolo8.darkbot.extensions.plugins.PluginListener; +import com.github.manolo8.darkbot.utils.I18n; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +public class FeatureRegistry implements PluginListener { + + private final PluginHandler pluginHandler; + private final Map> FEATURES_BY_ID = new LinkedHashMap<>(); + private final FeatureInstanceLoader featureLoader; + private final FeatureRegisterHandler registryHandler; + + public FeatureRegistry(Main main, PluginHandler pluginHandler) { + this.pluginHandler = pluginHandler; + this.featureLoader = new FeatureInstanceLoader(main); + this.registryHandler = new FeatureRegisterHandler(main, this); + pluginHandler.addListener(this); + } + + @Override + public void beforeLoad() { + FEATURES_BY_ID.values() + .stream() + .map(FeatureDefinition::getInstance) + .filter(Objects::nonNull) + .forEach(featureLoader::unloadFeature); + FEATURES_BY_ID.clear(); + registryHandler.getNativeFeatures().forEach(this::registerNativeFeature); + registryHandler.update(); + } + + @Override + public void afterLoad() { + pluginHandler.LOADED_PLUGINS.forEach(pl -> + Arrays.stream(pl.getDefinition().features) + .forEach(feature -> registerPluginFeature(pl, feature))); + registryHandler.update(); + } + + public void updateConfig() { + FEATURES_BY_ID.values().forEach(featureLoader::updateConfig); + } + + private void registerNativeFeature(Class clazz) { + FEATURES_BY_ID.put(clazz.getCanonicalName(), new FeatureDefinition<>(null, clazz)); + } + + private void registerPluginFeature(Plugin plugin, String clazzName) { + try { + Class feature = pluginHandler.PLUGIN_CLASS_LOADER.loadClass(clazzName); + FeatureDefinition fd = new FeatureDefinition<>(plugin, feature); + fd.addStatusListener(def -> registryHandler.update()); + fd.getIssues().addListener(iss -> registryHandler.update()); + FEATURES_BY_ID.put(clazzName, fd); + } catch (Throwable e) { + plugin.getIssues().addWarning(I18n.get("bot.issue.feature.failed_to_load"), I18n.get("bot.issue.feature.failed_to_load.desc", clazzName, e.toString())); + } + } + + private Optional getFeature(String id) { + synchronized (pluginHandler) { + FeatureDefinition feature = getFeatureDefinition(id); + try { + if (feature == null || !feature.canLoad()) return Optional.empty(); + + T instance = feature.getInstance(); + if (instance != null) return Optional.of(instance); + + feature.setInstance(instance = featureLoader.loadFeature(feature)); + return Optional.of(instance); + } catch (Throwable e) { + feature.getIssues().addFailure(I18n.get("bot.issue.feature.failed_to_load"), IssueHandler.createDescription(e)); + e.printStackTrace(); + return Optional.empty(); + } + } + } + + public Optional getFeature(Class feature) { + return getFeature(feature.getCanonicalName(), feature); + } + + public Optional getFeature(String id, Class type) { + return getFeature(id); + } + + public Optional getFeature(FeatureDefinition fd) { + return getFeature(fd.getId()); + } + + public Stream> getFeatures(Class type) { + //noinspection unchecked + return FEATURES_BY_ID + .values() + .stream() + .filter(FeatureDefinition::canLoad) + .filter(fd -> type.isAssignableFrom(fd.getClazz())) + .map(fd -> (FeatureDefinition) fd); + } + + public Stream> getFeatures(Plugin plugin) { + return FEATURES_BY_ID + .values() + .stream() + .filter(fd -> fd.getPlugin() == plugin); + } + + public FeatureDefinition getFeatureDefinition(T feature) { + return getFeatureDefinition(feature.getClass().getCanonicalName()); + } + + public FeatureDefinition getFeatureDefinition(String id) { + synchronized (pluginHandler) { + //noinspection unchecked + return (FeatureDefinition) FEATURES_BY_ID.get(id); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/ConfigurableDecorator.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/ConfigurableDecorator.java new file mode 100644 index 00000000..dce8692f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/ConfigurableDecorator.java @@ -0,0 +1,53 @@ +package com.github.manolo8.darkbot.extensions.features.decorators; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Configurable; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.utils.ReflectionUtils; +import com.google.gson.JsonElement; + +import java.lang.reflect.Type; +import java.util.Map; + +public class ConfigurableDecorator extends FeatureDecorator { + + private final Main main; + + public ConfigurableDecorator(Main main) { + this.main = main; + } + + @Override + protected void load(FeatureDefinition fd, Configurable obj) { + String id = obj.getClass().getCanonicalName(); + Type[] configParams = ReflectionUtils.findGenericParameters(obj.getClass(), Configurable.class); + if (configParams == null || configParams.length == 0) { + fd.getIssues().addWarning("Config not loaded", "Could not find config type, so it can't be loaded!"); + return; + } + Class configClass = (Class) configParams[0]; + if (configClass == null) return; + + Object config = toConfig(main.config.CUSTOM_CONFIGS.get(id), configClass); + main.config.CUSTOM_CONFIGS.put(id, config); + + //noinspection unchecked + obj.setConfig(config); + } + + @Override + protected void unload(Configurable obj) { + String id = obj.getClass().getCanonicalName(); + main.config.CUSTOM_CONFIGS.put(id, toJsonElement(main.config.CUSTOM_CONFIGS.get(id))); + } + + private JsonElement toJsonElement(Object config) { + return config instanceof JsonElement ? (JsonElement) config : Main.GSON.toJsonTree(config); + } + + private T toConfig(Object config, Class type) { + if (config == null) return ReflectionUtils.createInstance(type); + return type.isInstance(config) ? type.cast(config) : Main.GSON.fromJson(toJsonElement(config), type); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/FeatureDecorator.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/FeatureDecorator.java new file mode 100644 index 00000000..050242ed --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/FeatureDecorator.java @@ -0,0 +1,34 @@ +package com.github.manolo8.darkbot.extensions.features.decorators; + +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import java.lang.reflect.Type; + +public abstract class FeatureDecorator { + private final Class handledType; + + public FeatureDecorator() { + Type[] types = ReflectionUtils.findGenericParameters(getClass(), FeatureDecorator.class); + if (types == null) + throw new UnsupportedOperationException("Can't initialize feature decorator with no found type: " + getClass().getCanonicalName()); + + //noinspection unchecked + handledType = (Class) types[0]; + } + + public final void tryLoad(FeatureDefinition fd, F obj) { + if (!handledType.isInstance(obj)) return; + //noinspection unchecked + load((FeatureDefinition) fd, (T) obj); + } + protected abstract void load(FeatureDefinition fd, T obj); + + public final void tryUnload(Object obj) { + if (!handledType.isInstance(obj)) return; + unload(handledType.cast(obj)); + } + protected abstract void unload(T obj); +} + + diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/InstallableDecorator.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/InstallableDecorator.java new file mode 100644 index 00000000..4d5f6bc4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/InstallableDecorator.java @@ -0,0 +1,25 @@ +package com.github.manolo8.darkbot.extensions.features.decorators; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Installable; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; + +public class InstallableDecorator extends FeatureDecorator { + + private final Main main; + + public InstallableDecorator(Main main) { + this.main = main; + } + + @Override + protected void load(FeatureDefinition fd, Installable obj) { + obj.install(main); + } + + @Override + protected void unload(Installable obj) { + obj.uninstall(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/InstructionProviderDecorator.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/InstructionProviderDecorator.java new file mode 100644 index 00000000..8331e8a5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/decorators/InstructionProviderDecorator.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.extensions.features.decorators; + +import com.github.manolo8.darkbot.core.itf.InstructionProvider; +import com.github.manolo8.darkbot.core.itf.Module; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.modules.TemporalModule; + +public class InstructionProviderDecorator extends FeatureDecorator { + + @Override + protected void load(FeatureDefinition fd, InstructionProvider obj) { + if (obj instanceof Module && !(obj instanceof TemporalModule)) obj.showInstructions(fd.getName()); + } + + @Override + protected void unload(InstructionProvider obj) { + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/BehaviourHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/BehaviourHandler.java new file mode 100644 index 00000000..466e70bf --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/BehaviourHandler.java @@ -0,0 +1,39 @@ +package com.github.manolo8.darkbot.extensions.features.handlers; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Behaviour; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.features.FeatureRegistry; + +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BehaviourHandler extends FeatureHandler { + + private static final Class[] NATIVE = new Class[]{}; + + private final Main main; + private final FeatureRegistry featureRegistry; + + public BehaviourHandler(Main main, FeatureRegistry featureRegistry) { + this.main = main; + this.featureRegistry = featureRegistry; + } + + @Override + public Class[] getNativeFeatures() { + return NATIVE; + } + + @Override + public void update(Stream> behaviours) { + main.setBehaviours(behaviours + .map(featureRegistry::getFeature) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList())); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/ExtraMenuHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/ExtraMenuHandler.java new file mode 100644 index 00000000..83226c2b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/ExtraMenuHandler.java @@ -0,0 +1,33 @@ +package com.github.manolo8.darkbot.extensions.features.handlers; + +import com.github.manolo8.darkbot.core.itf.ExtraMenuProvider; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.features.FeatureRegistry; +import com.github.manolo8.darkbot.gui.titlebar.ExtraButton; + +import java.util.Optional; +import java.util.stream.Stream; + +public class ExtraMenuHandler extends FeatureHandler { + + private static final Class[] NATIVE = new Class[]{ExtraButton.DefaultExtraMenuProvider.class}; + + private final FeatureRegistry featureRegistry; + + public ExtraMenuHandler(FeatureRegistry featureRegistry) { + this.featureRegistry = featureRegistry; + } + + @Override + public Class[] getNativeFeatures() { + return NATIVE; + } + + @Override + public void update(Stream> extras) { + ExtraButton.setExtraDecorations(extras + .map(featureRegistry::getFeature) + .filter(Optional::isPresent) + .map(Optional::get)); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/FeatureHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/FeatureHandler.java new file mode 100644 index 00000000..846cae86 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/FeatureHandler.java @@ -0,0 +1,29 @@ +package com.github.manolo8.darkbot.extensions.features.handlers; + +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import java.lang.reflect.Type; +import java.util.stream.Stream; + +public abstract class FeatureHandler { + + private final Class handledType; + + public FeatureHandler() { + Type[] types = ReflectionUtils.findGenericParameters(getClass(), FeatureHandler.class); + if (types == null) + throw new UnsupportedOperationException("Can't initialize feature decorator with no found type: " + getClass().getCanonicalName()); + + //noinspection unchecked + handledType = (Class) types[0]; + } + + public Class getHandledType() { + return handledType; + } + + public abstract Class[] getNativeFeatures(); + public abstract void update(Stream> features); + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/ModuleHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/ModuleHandler.java new file mode 100644 index 00000000..96c0f8fe --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/ModuleHandler.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.extensions.features.handlers; + +import com.github.manolo8.darkbot.config.types.suppliers.ModuleSupplier; +import com.github.manolo8.darkbot.core.itf.Module; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.modules.CollectorModule; +import com.github.manolo8.darkbot.modules.LootModule; +import com.github.manolo8.darkbot.modules.LootNCollectorModule; + +import java.util.LinkedHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ModuleHandler extends FeatureHandler { + + private static final Class[] NATIVE = new Class[]{CollectorModule.class, LootModule.class, LootNCollectorModule.class}; + + @Override + public Class[] getNativeFeatures() { + return NATIVE; + } + + @Override + public void update(Stream> modules) { + ModuleSupplier.updateModules(modules.collect( + Collectors.toMap(FeatureDefinition::getId, Function.identity(), (a, b) -> a, LinkedHashMap::new))); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/NpcExtraHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/NpcExtraHandler.java new file mode 100644 index 00000000..09384457 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/NpcExtraHandler.java @@ -0,0 +1,35 @@ +package com.github.manolo8.darkbot.extensions.features.handlers; + +import com.github.manolo8.darkbot.config.NpcExtra; +import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.core.itf.NpcExtraProvider; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.features.FeatureRegistry; +import com.github.manolo8.darkbot.gui.titlebar.ExtraButton; + +import java.util.Optional; +import java.util.stream.Stream; + +public class NpcExtraHandler extends FeatureHandler { + + private static final Class[] NATIVE = new Class[]{NpcExtra.DefaultNpcExtraProvider.class}; + + private final FeatureRegistry featureRegistry; + + public NpcExtraHandler(FeatureRegistry featureRegistry) { + this.featureRegistry = featureRegistry; + } + + @Override + public Class[] getNativeFeatures() { + return NATIVE; + } + + @Override + public void update(Stream> flags) { + NpcInfo.setNpcFlags(flags + .map(featureRegistry::getFeature) + .filter(Optional::isPresent) + .map(Optional::get)); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/TaskHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/TaskHandler.java new file mode 100644 index 00000000..8d0cd9b9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/features/handlers/TaskHandler.java @@ -0,0 +1,40 @@ +package com.github.manolo8.darkbot.extensions.features.handlers; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.backpage.FlashResManager; +import com.github.manolo8.darkbot.backpage.UsernameUpdater; +import com.github.manolo8.darkbot.core.itf.Task; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.features.FeatureRegistry; + +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TaskHandler extends FeatureHandler { + + private static final Class[] NATIVE = new Class[]{UsernameUpdater.class, FlashResManager.class}; + + private final Main main; + private final FeatureRegistry featureRegistry; + + public TaskHandler(Main main, FeatureRegistry featureRegistry) { + this.main = main; + this.featureRegistry = featureRegistry; + } + + @Override + public Class[] getNativeFeatures() { + return NATIVE; + } + + @Override + public void update(Stream> tasks) { + main.backpage.setTasks(tasks + .map(featureRegistry::getFeature) + .map(o -> o.orElse(null)) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/IssueHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/IssueHandler.java new file mode 100644 index 00000000..4d93615d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/IssueHandler.java @@ -0,0 +1,72 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +import com.github.manolo8.darkbot.core.utils.Lazy; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class IssueHandler { + private final IssueHandler parent; + + private final Set issues = new TreeSet<>(); + private final Lazy listener = new Lazy.NoCache<>(); + + public IssueHandler() { + this(null); + } + + public IssueHandler(IssueHandler parent) { + this.parent = parent; + } + + public void addInfo(String message, String description) { + add(message, description, PluginIssue.Level.INFO); + } + + public void addWarning(String message, String description) { + add(message, description, PluginIssue.Level.WARNING); + } + + public void addFailure(String message, String description) { + add(message, description, PluginIssue.Level.ERROR); + } + + public static String createDescription(Throwable e) { + return Stream.concat( + Stream.of("" + e.toString() + ""), + Arrays.stream(e.getStackTrace()) + ).map(Objects::toString) + .limit(100) + .collect(Collectors.joining("
", "", "")); + } + + public void add(String message, String description, PluginIssue.Level level) { + if (this.issues.add(new PluginIssue(message, description, level))) + listener.send(this); + } + + public void addListener(Consumer listener) { + this.listener.add(listener); + } + + public Set getIssues() { + return issues; + } + + /** + * @return The highest issue level. + */ + public PluginIssue.Level getLevel() { + return issues.isEmpty() ? null : issues.iterator().next().getLevel(); + } + + public boolean canLoad() { + return getLevel() != PluginIssue.Level.ERROR && (parent == null || parent.canLoad()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/Plugin.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/Plugin.java new file mode 100644 index 00000000..64ef3beb --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/Plugin.java @@ -0,0 +1,52 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.PluginInfo; + +import java.io.File; +import java.net.URL; + +public class Plugin { + + private final File file; + private final URL jar; + + private PluginDefinition definition; + private PluginInfo info; + private IssueHandler issues = new IssueHandler(); + + public Plugin(File file, URL jar) { + this.file = file; + this.jar = jar; + } + + public File getFile() { + return file; + } + + public URL getJar() { + return jar; + } + + public void setDefinition(PluginDefinition definition) { + this.definition = definition; + info = ConfigEntity.INSTANCE.getPluginInfo(definition); + } + + public PluginDefinition getDefinition() { + return definition; + } + + public PluginInfo getInfo() { + return info; + } + + public IssueHandler getIssues() { + return issues; + } + + public String getName() { + return definition != null ? definition.name : new File(jar.getFile()).getName(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginDefinition.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginDefinition.java new file mode 100644 index 00000000..92e7f4cf --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginDefinition.java @@ -0,0 +1,29 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +import com.github.manolo8.darkbot.extensions.util.Version; + +import java.net.URI; +import java.net.URL; + +public class PluginDefinition { + private transient final String[] NULL_ARRAY = new String[0]; + + // Name of the plugin + public String name; + // Author of the plugin + public String author; + // Version of the plugin + public Version version; + // Minimum version this plugin requires to run + public Version minVersion; + // Latest tested version this plugin runs on + public Version supportedVersion; + // Array of fully qualified class names of modules + public String[] features = NULL_ARRAY; + // Donation url + public URI donation; + // URL to download the plugin from + public URL download; + // URI to get an updated plugin definition (a plugin.json) + public URL update; +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginHandler.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginHandler.java new file mode 100644 index 00000000..35397bf7 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginHandler.java @@ -0,0 +1,187 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.utils.AuthAPI; +import com.github.manolo8.darkbot.utils.I18n; +import com.google.gson.Gson; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +public class PluginHandler { + private static final Gson GSON = new Gson(); + + public static final File PLUGIN_FOLDER = new File("plugins"), + PLUGIN_UPDATE_FOLDER = new File("plugins/updates"); + public static final Path PLUGIN_PATH = PLUGIN_FOLDER.toPath(), + PLUGIN_UPDATE_PATH = PLUGIN_UPDATE_FOLDER.toPath(); + + public URLClassLoader PLUGIN_CLASS_LOADER; + public List LOADED_PLUGINS = new ArrayList<>(); + public List FAILED_PLUGINS = new ArrayList<>(); + public List LOADING_EXCEPTIONS = new ArrayList<>(); + + private static List LISTENERS = new ArrayList<>(); + + public void addListener(PluginListener listener) { + if (!LISTENERS.contains(listener)) LISTENERS.add(listener); + } + + private final Object BACKGROUND_LOCK = new Object(); + public Object getBackgroundLock() { + return BACKGROUND_LOCK; + } + + private File[] getJars(String folder) { + File[] jars = new File(folder).listFiles((dir, name) -> name.endsWith(".jar")); + return jars != null ? jars : new File[0]; + } + + /** Updates plugins asynchronously */ + public void updatePlugins() { + Thread pluginReloader = new Thread(() -> { + synchronized (getBackgroundLock()) { + try { + SwingUtilities.invokeAndWait(this::updatePluginsInternal); + } catch (InterruptedException | InvocationTargetException e) { + e.printStackTrace(); + } + } + }); + pluginReloader.setDaemon(true); + pluginReloader.start(); + } + + public void updatePluginsSync() { + synchronized (getBackgroundLock()) { + updatePluginsInternal(); + } + } + + private void updatePluginsInternal() { + synchronized (this) { + LOADED_PLUGINS.clear(); + FAILED_PLUGINS.clear(); + LOADING_EXCEPTIONS.clear(); + LISTENERS.forEach(PluginListener::beforeLoad); + System.gc(); + + if (PLUGIN_CLASS_LOADER != null) { + try { + PLUGIN_CLASS_LOADER.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + for (File plugin : getJars("plugins/updates")) { + Path plPath = plugin.toPath(); + try { + Files.move(plPath, PLUGIN_PATH.resolve(plPath.getFileName()), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + LOADING_EXCEPTIONS.add(new PluginLoadingException("Failed to update plugin: " + plPath.getFileName(), e)); + e.printStackTrace(); + } + } + try { + loadPlugins(getJars("plugins")); + } catch (Exception e) { + LOADING_EXCEPTIONS.add(new PluginLoadingException("Failed to load plugins", e)); + e.printStackTrace(); + } + LISTENERS.forEach(PluginListener::afterLoad); + } + LISTENERS.forEach(PluginListener::afterLoadComplete); + } + + private void loadPlugins(File[] pluginFiles) { + for (File pluginFile : pluginFiles) { + Plugin pl = null; + try { + pl = new Plugin(pluginFile, pluginFile.toURI().toURL()); + loadPlugin(pl); + if (pl.getIssues().canLoad()) LOADED_PLUGINS.add(pl); + else FAILED_PLUGINS.add(pl); + } catch (PluginLoadingException e) { + LOADING_EXCEPTIONS.add(e); + e.printStackTrace(); + } catch (Throwable e) { + LOADING_EXCEPTIONS.add(new PluginLoadingException("Failed to load plugin", e, pl)); + e.printStackTrace(); + } + } + PLUGIN_CLASS_LOADER = new URLClassLoader(LOADED_PLUGINS.stream().map(Plugin::getJar).toArray(URL[]::new)); + } + + private void loadPlugin(Plugin plugin) throws IOException, PluginLoadingException { + try (JarFile jar = new JarFile(plugin.getFile(), true)) { + ZipEntry plJson = jar.getEntry("plugin.json"); + if (plJson == null) { + throw new PluginLoadingException("The plugin is missing a plugin.json in the jar root", plugin); + } + try (InputStreamReader isr = new InputStreamReader(jar.getInputStream(plJson), StandardCharsets.UTF_8)) { + PluginDefinition plDef = GSON.fromJson(isr, (Type) PluginDefinition.class); + plugin.setDefinition(plDef); + } + testUnique(plugin); + testCompatibility(plugin); + testSignature(plugin, jar); + } + } + + private void testUnique(Plugin plugin) { + if (LOADED_PLUGINS.stream().anyMatch(other -> other.getName().equals(plugin.getName()))) + plugin.getIssues().addFailure(I18n.get("plugins.issues.loaded_twice"), + I18n.get("plugins.issues.loaded_twice.desc")); + } + + private void testCompatibility(Plugin plugin) { + PluginDefinition pd = plugin.getDefinition(); + + if (pd.minVersion.compareTo(pd.supportedVersion) > 0) + plugin.getIssues().addFailure(I18n.get("plugins.issues.invalid_json"), + I18n.get("plugins.issues.invalid_json.desc", pd.minVersion, pd.supportedVersion)); + + String supportedRange = "DarkBot v" + (pd.minVersion.compareTo(pd.supportedVersion) == 0 ? + pd.minVersion : pd.minVersion + "-v" + pd.supportedVersion); + + if (Main.VERSION.compareTo(pd.minVersion) < 0) + plugin.getIssues().addFailure(I18n.get("plugins.issues.bot_update"), + I18n.get("plugins.issues.bot_update.desc", supportedRange, Main.VERSION)); + + if (Main.VERSION.compareTo(pd.supportedVersion) > 0) + plugin.getIssues().addInfo(I18n.get("plugins.issues.plugin_update"), + I18n.get("plugins.issues.plugin_update.desc", supportedRange, Main.VERSION)); + } + + private void testSignature(Plugin plugin, JarFile jar) throws IOException { + try { + Boolean signatureValid = AuthAPI.getInstance().checkPluginJarSignature(jar); + if (signatureValid == null) + plugin.getIssues().addFailure(I18n.get("plugins.issues.signature.plugin_not_signed"), + I18n.get("plugins.issues.signature.plugin_not_signed.desc")); + else if (!signatureValid) + plugin.getIssues().addFailure(I18n.get("plugins.issues.signature.unknown_signature"), + I18n.get("plugins.issues.signature.unknown_signature.desc")); + } catch (SecurityException e) { + plugin.getIssues().addFailure(I18n.get("plugins.issues.signature.invalid_signature"), + I18n.get("plugins.issues.signature.invalid_signature.desc")); + } + } + + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginIssue.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginIssue.java new file mode 100644 index 00000000..a5fc4d53 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginIssue.java @@ -0,0 +1,60 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +import java.util.Objects; + +public class PluginIssue implements Comparable { + + public enum Level { + INFO, WARNING, ERROR; + } + + private final String message, description; + private final Level level; + + public PluginIssue(String message, String description, Level level) { + Objects.requireNonNull(message, "Message must not be null"); + Objects.requireNonNull(description, "Description must not be null"); + Objects.requireNonNull(level, "Description must not be null"); + this.message = message; + this.description = description; + this.level = level; + } + + public String getMessage() { + return message; + } + + public String getDescription() { + return description; + } + + public boolean preventsLoading() { + return level == Level.ERROR; + } + + public Level getLevel() { + return level; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PluginIssue that = (PluginIssue) o; + return level == that.level && + message.equals(that.message) && + description.equals(that.description); + } + + @Override + public int hashCode() { + return Objects.hash(message, description, level); + } + + @Override + public int compareTo(PluginIssue o) { + if (level != o.level) return o.level.compareTo(level); // Severe levels first + if (!message.equals(o.message)) return message.compareTo(o.message); + return description.compareTo(o.description); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginListener.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginListener.java new file mode 100644 index 00000000..8f6e3594 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginListener.java @@ -0,0 +1,7 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +public interface PluginListener { + default void beforeLoad(){} + default void afterLoad(){} + default void afterLoadComplete() {} +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginLoadingException.java b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginLoadingException.java new file mode 100644 index 00000000..500ce07f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/plugins/PluginLoadingException.java @@ -0,0 +1,28 @@ +package com.github.manolo8.darkbot.extensions.plugins; + +public class PluginLoadingException extends Exception { + + private Plugin plugin; + + public PluginLoadingException(String message, Throwable cause, Plugin plugin) { + super(message, cause); + this.plugin = plugin; + } + + public PluginLoadingException(String message, Plugin plugin) { + super(message); + this.plugin = plugin; + } + + public PluginLoadingException(String message, Throwable cause) { + super(message, cause); + } + + public PluginLoadingException(String message) { + super(message); + } + + public Plugin getPlugin() { + return plugin; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/util/VerifierChecker.java b/src/main/java/com/github/manolo8/darkbot/extensions/util/VerifierChecker.java new file mode 100644 index 00000000..431fdc7d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/util/VerifierChecker.java @@ -0,0 +1,125 @@ +package com.github.manolo8.darkbot.extensions.util; + +import com.github.manolo8.darkbot.utils.AuthAPI; + +import java.io.File; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.Base64; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class VerifierChecker { + + private static final String META_INF = "META-INF/"; + private static final String SIG_PREFIX = META_INF + "SIG-"; + private static final byte[] POPCORN_PUB = Base64.getDecoder().decode("MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzqOpdk4bdoMlk3IkDaHFSOpwyYmpfACHCuhNDiml13Wf9J4D9g4kszOV3Qz+FT1jdYO36pWCxI01Mr03dPLky9COwD//dQM/KRFBe7Z0wRsC91n5fprgWIkwdKs79en6vmynyyPi5hAgwpifKm4o9DP5xR0YP/KRoPH8ZekS+STBxPsLdy/BeBiFFFgNQ0usRNIkLBKYWFJ3A3br4QkVicOLvycHKrfsN9K2Ly25VXyYo/GJdeEY30ixKhsCdo9xc50ERVuEVkzqlqLUSFDgHyFAO1o91QIhG+G0GURlI8iSt/b5cn39DM0OtkL+1TqqwT4NJqBH8nHSok8lReu1o/iMu9VbrFyJTUK0qUjVhnySJQV3i5oV0oxwqPodDihvmNUhMUel5gM/yRnloKKEYk+74MLdClgcFWmbEYFUQF32vxdkKpGYYRmzH0Y8+pGKE8nBbe1/eKg2HVu42vStb/yKp7DpxQ05UovJ5nrXA7lUfwCwBOwzOmCjn3AKNhH+Hbg/tutwZn5KNU4zJCRUEM4FLkCCJMEDJTGnpjxNO/vUMEm+Co6RgrD1vBIgRzNxaYh1BInbDdlKncXhysHNR5b6Et2POyCrlrM4flvFvTg42/zbI1ElKgEFNbhujdP5fBtxeD1hkc5UUa8JtYHsHa0LBrTUfnr3F29rRwHFpFUCAwEAAQ=="); + + public static AuthAPI getAuthApi() { + AuthAPI instance = AuthAPI.getInstance(); + try (JarFile jf = new JarFile(findPathJar(instance.getClass()), true)) { + Vector entriesVec = new Vector<>(); + byte[] buffer = new byte[8192]; + + Enumeration entries = jf.entries(); + while (entries.hasMoreElements()) { + JarEntry je = entries.nextElement(); + entriesVec.addElement(je); + + try (InputStream is = jf.getInputStream(je)) { + //noinspection StatementWithEmptyBody + while (is.read(buffer, 0, buffer.length) != -1) ; + // we just read. this will throw a SecurityException + // if a signature/digest check fails. + } + } + + Manifest man = jf.getManifest(); + + if (man == null) throw new SecurityException("Verifier not signed"); + Enumeration e = entriesVec.elements(); + + // Used to cache allowed certs, no longer needing to check pub byte array for them + Set allowedCerts = new HashSet<>(); + + while (e.hasMoreElements()) { + JarEntry je = e.nextElement(); + String name = je.getName(); + if (je.isDirectory() || signatureRelated(name)) continue; + + Boolean signed = checkCertificates(je.getCertificates(), allowedCerts); + if (signed == null || !signed) throw new SecurityException("Verifier not properly signed"); + } + } catch (Exception e) { + throw new SecurityException("Failed to check verifier signature", e); + } + return instance; + } + + private static Boolean checkCertificates(Certificate[] certs, Set allowedCerts) { + if (certs == null || certs.length == 0) return null; + for (Certificate cert : certs) { + if (allowedCerts.contains(cert)) return true; + if (Arrays.equals(POPCORN_PUB, cert.getPublicKey().getEncoded())) { + allowedCerts.add(cert); + return true; + } + } + return false; + } + + private static boolean signatureRelated(String name) { + String ucName = name.toUpperCase(Locale.ENGLISH); + if (ucName.equals(JarFile.MANIFEST_NAME) || + ucName.equals(META_INF) || + (ucName.startsWith(SIG_PREFIX) && + ucName.indexOf("/") == ucName.lastIndexOf("/"))) { + return true; + } + + return ucName.startsWith(META_INF) + && (ucName.endsWith(".SF") || ucName.endsWith(".DSA") || ucName.endsWith(".RSA") || ucName.endsWith(".EC")) + && (ucName.indexOf("/") == ucName.lastIndexOf("/")); + } + + // https://stackoverflow.com/a/1983875 + private static String findPathJar(Class context) throws IllegalStateException { + String rawName = context.getName(); + String classFileName; + /* rawName is something like package.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ { + int idx = rawName.lastIndexOf('.'); + classFileName = (idx == -1 ? rawName : rawName.substring(idx+1)) + ".class"; + } + + String uri = context.getResource(classFileName).toString(); + if (uri.startsWith("file:")) throw new IllegalStateException("This class has been loaded from a directory and not from a jar file."); + if (!uri.startsWith("jar:file:")) { + int idx = uri.indexOf(':'); + String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); + throw new IllegalStateException("This class has been loaded remotely via the " + protocol + + " protocol. Only loading from a jar on the local file system is supported."); + } + + int idx = uri.indexOf('!'); + //As far as I know, the if statement below can't ever trigger, so it's more of a sanity check thing. + if (idx == -1) throw new IllegalStateException("You appear to have loaded this class from a local jar file, but I can't make sense of the URL!"); + + try { + String fileName = URLDecoder.decode(uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name()); + return new File(fileName).getAbsolutePath(); + } catch (UnsupportedEncodingException e) { + throw new InternalError("default charset doesn't exist. Your VM is borked."); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/extensions/util/Version.java b/src/main/java/com/github/manolo8/darkbot/extensions/util/Version.java new file mode 100644 index 00000000..dcac7d8c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/extensions/util/Version.java @@ -0,0 +1,91 @@ +package com.github.manolo8.darkbot.extensions.util; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@JsonAdapter(Version.VersionAdapter.class) +public class Version implements Comparable { + private static final Pattern VERSION = Pattern.compile("" + + "([^0-9]*[0-9]+)" + // Major + "(\\.([0-9]+))?" + // Minor + "(\\.([0-9]+))?" + // Patch + "(\\.([0-9]+))?" + // Revision + "( ?beta ?([0-9]+)?)?" + // Beta + "( ?alpha ?([0-9]+)?)?"); // Alpha + + private String version; + private int major, minor, patch, revision, beta, alpha; + + public Version(String version) { + this.version = version; + Matcher matcher = VERSION.matcher(version); + if (!matcher.matches()) throw new IllegalArgumentException("Couldn't parse version " + version); + major = Integer.parseInt(matcher.group(1)); + minor = getInt(matcher, 2); + patch = getInt(matcher, 4); + revision = getInt(matcher, 6); + beta = getInt(matcher, 8); + alpha = getInt(matcher, 10); + if (beta == -1 && alpha == -1) beta = Integer.MAX_VALUE; + if (alpha == -1) alpha = Integer.MAX_VALUE; + } + + private int getInt(Matcher m, int find) { + if (m.group(find) == null) return -1; + String num = m.group(find + 1); + return num == null ? 0 : Integer.parseInt(m.group(find + 1)); + } + + @Override + public int compareTo(Version o) { + if (major != o.major) return Integer.compare(major, o.major); + if (minor != o.minor) return Integer.compare(minor, o.minor); + if (patch != o.patch) return Integer.compare(patch, o.patch); + if (revision != o.revision) return Integer.compare(revision, o.revision); + if (beta != o.beta) return Integer.compare(beta, o.beta); + if (alpha != o.alpha) return Integer.compare(alpha, o.alpha); + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Version v = (Version) o; + return major == v.major && + minor == v.minor && + patch == v.patch && + revision == v.revision && + beta == v.beta && + alpha == v.alpha; + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch, revision, beta, alpha); + } + + @Override + public String toString() { + return version; + } + + public static class VersionAdapter extends TypeAdapter { + @Override + public void write(JsonWriter writer, Version value) throws IOException { + writer.value(value.toString()); + } + + @Override + public Version read(JsonReader in) throws IOException { + return new Version(in.nextString()); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/AdvancedConfig.java b/src/main/java/com/github/manolo8/darkbot/gui/AdvancedConfig.java new file mode 100644 index 00000000..acc597a6 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/AdvancedConfig.java @@ -0,0 +1,139 @@ +package com.github.manolo8.darkbot.gui; + +import com.github.manolo8.darkbot.config.tree.ConfigTree; +import com.github.manolo8.darkbot.extensions.plugins.PluginListener; +import com.github.manolo8.darkbot.gui.utils.SearchField; +import com.github.manolo8.darkbot.gui.tree.EditorManager; +import com.github.manolo8.darkbot.gui.tree.TreeEditor; +import com.github.manolo8.darkbot.gui.tree.TreeRenderer; +import com.github.manolo8.darkbot.gui.utils.SimpleTreeListener; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.plaf.LayerUI; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.awt.event.MouseWheelEvent; + +public class AdvancedConfig extends JPanel implements PluginListener { + + public static final int EDITOR_HEIGHT = 17; + public static final int ROW_HEIGHT = 18; + public static final int HEADER_HEIGHT = 26; + + private Object config; + private ConfigTree treeModel; + private boolean packed = false; // If this is a packed config in a floating window + + public AdvancedConfig() { + setLayout(new MigLayout("ins 0, gap 0, fill, wrap 1", "[]", "[][grow]")); + } + + public AdvancedConfig(Object config) { + setLayout(new BorderLayout()); + packed = true; + setEditingConfig(config); + } + + void setEditingConfig(Object config) { + if (config == null) return; + removeAll(); + this.config = config; + this.treeModel = new ConfigTree(config); + if (!packed) { + add(new SearchField(treeModel::setFilter), "grow"); + add(setupUI(), "grow"); + } else { + add(setupUI()); + } + this.revalidate(); + this.repaint(); + } + + @Override + public void beforeLoad() { + removeAll(); + } + + @Override + public void afterLoadComplete() { + setEditingConfig(config); + } + + public void setCustomConfig(String name, Object config) { + treeModel.setCustom(name, config); + } + + private JComponent setupUI() { + JTree configTree = new JTree(treeModel); + configTree.setEditable(true); + configTree.setRootVisible(false); + configTree.setShowsRootHandles(true); + configTree.setToggleClickCount(1); + configTree.setRowHeight(0); + configTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + + ToolTipManager.sharedInstance().registerComponent(configTree); + + EditorManager editors = new EditorManager(); + + configTree.setCellRenderer(new TreeRenderer(editors)); + configTree.setCellEditor(new TreeEditor(configTree, new EditorManager(editors))); + + treeModel.addTreeModelListener((SimpleTreeListener) e -> { + unfoldTopLevelTree(configTree); + SwingUtilities.invokeLater(() -> { + configTree.validate(); + configTree.repaint(); + }); + }); + unfoldTopLevelTree(configTree); + + JScrollPane scrollPane = new JScrollPane(configTree); + scrollPane.setBorder(null); + scrollPane.getVerticalScrollBar().setUnitIncrement(25); + + if (packed) { + Dimension treeSize = configTree.getPreferredSize(); + scrollPane.setPreferredSize(new Dimension(treeSize.width + 15, Math.min(400, treeSize.height))); + } + return new JLayer<>(scrollPane, new WheelScrollLayerUI()); + } + + private void unfoldTopLevelTree(JTree configTree) { + for (int i = configTree.getRowCount() - 1; i >= 0; i--) configTree.expandRow(i); + } + + public static Dimension forcePreferredHeight(Dimension preferred) { + preferred.height = EDITOR_HEIGHT; + return preferred; + } + + // http://java-swing-tips.blogspot.jp/2014/09/forward-mouse-wheel-scroll-event-in.html + static class WheelScrollLayerUI extends LayerUI { + @Override + public void installUI(JComponent c) { + super.installUI(c); + if (c instanceof JLayer) ((JLayer) c).setLayerEventMask(AWTEvent.MOUSE_WHEEL_EVENT_MASK); + } + + @Override + public void uninstallUI(JComponent c) { + if (c instanceof JLayer) ((JLayer) c).setLayerEventMask(0); + super.uninstallUI(c); + } + + @Override + protected void processMouseWheelEvent(MouseWheelEvent e, JLayer l) { + Component child = e.getComponent(); + JScrollPane parent = l.getView(); + if (!(child instanceof JScrollPane) || child.equals(parent)) return; + BoundedRangeModel m = ((JScrollPane) child).getVerticalScrollBar().getModel(); + int dir = e.getWheelRotation(), extent = m.getExtent(), + minimum = m.getMinimum(), maximum = m.getMaximum(), value = m.getValue(); + if (value + extent >= maximum && dir > 0 || value <= minimum && dir < 0) + parent.dispatchEvent(SwingUtilities.convertMouseEvent(child, e, parent)); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/ConfigGui.java b/src/main/java/com/github/manolo8/darkbot/gui/ConfigGui.java index c5678fd5..c1c65d2c 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/ConfigGui.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/ConfigGui.java @@ -1,83 +1,48 @@ package com.github.manolo8.darkbot.gui; import com.github.manolo8.darkbot.Main; -import com.github.manolo8.darkbot.config.BoxInfo; import com.github.manolo8.darkbot.config.Config; -import com.github.manolo8.darkbot.config.NpcInfo; -import com.github.manolo8.darkbot.core.objects.Map; -import com.github.manolo8.darkbot.modules.CollectorModule; -import com.github.manolo8.darkbot.modules.LootModule; -import com.github.manolo8.darkbot.modules.LootNCollectorModule; +import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.components.TabbedPane; +import com.github.manolo8.darkbot.gui.players.PlayerEditor; +import com.github.manolo8.darkbot.gui.plugins.PluginDisplay; +import com.github.manolo8.darkbot.gui.titlebar.ConfigPicker; +import com.github.manolo8.darkbot.gui.zones.ZonesEditor; +import com.github.manolo8.darkbot.gui.zones.safety.SafetiesEditor; +import com.github.manolo8.darkbot.gui.titlebar.ConfigTitleBar; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.gui.utils.window.WindowUtils; +import com.github.manolo8.darkbot.gui.zones.ZoneEditor; +import net.miginfocom.swing.MigLayout; import javax.swing.*; -import javax.swing.table.DefaultTableModel; import java.awt.*; -import java.awt.event.ItemEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.function.Consumer; public class ConfigGui extends JFrame { private final Main main; - private final Config config; - private JTabbedPane tabbedPane; + private final JPanel mainPanel = new JPanel(); - private JPanel generalPane; - private JPanel collectPane; - private JPanel lootPane; - private JPanel ggPane; + private TabbedPane tabbedPane; - //GENERAL - private JComboBox workingMap; + private AdvancedConfig advancedPane; + private ZonesEditor zones; + private PlayerEditor playerEditor; + private ConfigPicker configPicker; + private MainButton pluginTab; + private PluginDisplay pluginDisplay; - private JComboBox reviveMethod; - - private JComboBox runConfig; - private JComboBox offensiveConfig; - - private JTextField runFormation; - private JTextField offensiveFormation; - - private JTextField refreshTime; - private JTextField maxDeaths; - - private JSlider repairHp; - private JSlider waitHp; - - private JRadioButton moduleCollector; - private JRadioButton moduleLoot; - private JRadioButton moduleLootNCollector; - //GENERAL - - //COLLECT - private JCheckBox stayAwayFromEnemies; - - private JCheckBox autoClock; - private JTextField autoCloackKey; - - private JTable boxTable; - //COLLECT - - //LOOT - private JCheckBox runFromEnemies; - private JCheckBox runFromEnemiesInSight; - - private JTextField ammoKey; - - private JCheckBox autoSab; - private JTextField ammoSabKey; - - private JTextField npcTableNameFilter; - private JComboBox npcTableMapFilter; - private JTable npcTable; - //LOOT + private Lazy stateChange = new Lazy.NoCache<>(); public ConfigGui(Main main) throws HeadlessException { - super("DarkBot - Config"); + super("DarkBot Configuration"); this.main = main; - this.config = main.config; setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); setSize(640, 480); @@ -86,776 +51,61 @@ public ConfigGui(Main main) throws HeadlessException { initComponents(); setComponentPosition(); setComponentData(); - setComponentEvent(); - validate(); - repaint(); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + main.config.changed = true; + stateChange.send(false); + } + }); + + WindowUtils.setupUndecorated(this, mainPanel); } private void initComponents() { - tabbedPane = new JTabbedPane(); - - generalPane = new JPanel(); - collectPane = new JPanel(); - lootPane = new JPanel(); - ggPane = new JPanel(); - - - //GENERAL - workingMap = new JComboBox<>(); - - reviveMethod = new JComboBox<>(); - - runConfig = new JComboBox<>(); - offensiveConfig = new JComboBox<>(); - - runFormation = new JTextField(); - offensiveFormation = new JTextField(); - - refreshTime = new JTextField(); - maxDeaths = new JTextField(); - - moduleCollector = new JRadioButton("Collector"); - moduleLoot = new JRadioButton("Loot"); - moduleLootNCollector = new JRadioButton("Loot and collector"); - - repairHp = new JSlider(); - waitHp = new JSlider(); - - ButtonGroup group = new ButtonGroup(); - group.add(moduleCollector); - group.add(moduleLoot); - group.add(moduleLootNCollector); - - //GENERAL - - //COLLECT - stayAwayFromEnemies = new JCheckBox("Stay away from enemies"); - - autoClock = new JCheckBox("Auto cloack"); - autoCloackKey = new JTextField(); + tabbedPane = new TabbedPane(); - boxTable = new JTable(new BoxTableModel()); - //COLLECT - - //LOOT - runFromEnemies = new JCheckBox("Run from enemies"); - runFromEnemiesInSight = new JCheckBox("Run from enemies in sight"); - - ammoKey = new JTextField(); - - autoSab = new JCheckBox("Auto SAB"); - ammoSabKey = new JTextField(); - - npcTableMapFilter = new JComboBox<>(); - npcTableNameFilter = new JTextField(); - npcTable = new JTable(new NpcTableModel()); - //LOOT + advancedPane = new AdvancedConfig(); + zones = new ZonesEditor(); + playerEditor = new PlayerEditor(); + configPicker = new ConfigPicker(); + pluginDisplay = new PluginDisplay(); } private void setComponentPosition() { + tabbedPane.addTab(null, "tabs.general", advancedPane); + tabbedPane.addTab(null, "tabs.zones", zones); + tabbedPane.addTab(null, "tabs.players", playerEditor); + pluginTab = tabbedPane.addHiddenTab(UIUtils.getIcon("plugins"), "tabs.plugins", pluginDisplay); - tabbedPane.addTab("General", generalPane); - tabbedPane.addTab("Collect", collectPane); - tabbedPane.addTab("Loot", lootPane); - tabbedPane.addTab("GG", ggPane); - - add(tabbedPane); - - //GENERAL - - generalPane.setLayout(new GridBagLayout()); - - GridBagConstraints c = new GridBagConstraints(); - - Insets normal = new Insets(0, 5, 5, 5); - Insets sep = new Insets(15, 5, 15, 5); - - c.fill = GridBagConstraints.BOTH; - - c.insets = normal; - - int y = 0; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Working map"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - generalPane.add(workingMap, c); - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - - c.insets = sep; - generalPane.add(separator(), c); - c.insets = normal; - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Running config"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 1; - generalPane.add(runConfig, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 2; - c.gridwidth = 1; - generalPane.add(new JLabel("Running formation"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 3; - c.gridwidth = 1; - generalPane.add(runFormation, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Offensive config"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 1; - generalPane.add(offensiveConfig, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 2; - c.gridwidth = 1; - generalPane.add(new JLabel("Offensive formation"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 3; - c.gridwidth = 1; - generalPane.add(offensiveFormation, c); - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - - c.insets = sep; - generalPane.add(separator(), c); - c.insets = normal; - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Refresh"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - generalPane.add(refreshTime, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Revive"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - generalPane.add(reviveMethod, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Deaths"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - generalPane.add(maxDeaths, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Repair Start %"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - generalPane.add(repairHp, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Repair Stop %"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - generalPane.add(waitHp, c); - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - - c.insets = sep; - generalPane.add(separator(), c); - c.insets = normal; - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - generalPane.add(new JLabel("Module"), c); - c.weightx = 0; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 1; - generalPane.add(moduleCollector, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 2; - c.gridwidth = 1; - generalPane.add(moduleLoot, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 3; - c.gridwidth = 1; - generalPane.add(moduleLootNCollector, c); - //GENERAL - - //COLLECT - collectPane.setLayout(new GridBagLayout()); - - c = new GridBagConstraints(); - - c.fill = GridBagConstraints.BOTH; - - c.ipady = 0; - c.ipadx = 0; - c.insets = normal; - - y = 0; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - collectPane.add(stayAwayFromEnemies, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 2; - collectPane.add(autoClock, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 2; - c.gridwidth = 1; - collectPane.add(new JLabel("Key"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 3; - c.gridwidth = 1; - collectPane.add(autoCloackKey, c); - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - - c.insets = sep; - collectPane.add(separator(), c); - c.insets = normal; - - ++y; - - c.weightx = 1; - c.weighty = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - collectPane.add(new JScrollPane(boxTable), c); - - //COLLECT - - //LOOT - lootPane.setLayout(new GridBagLayout()); - - c = new GridBagConstraints(); - - c.fill = GridBagConstraints.BOTH; - - c.ipady = 0; - c.ipadx = 0; - c.insets = normal; - - y = 0; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 2; - lootPane.add(runFromEnemies, c); - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 2; - lootPane.add(runFromEnemiesInSight, c); - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - lootPane.add(new JLabel("Normal Ammo"), c); - c.gridx = 2; - lootPane.add(new JLabel("KEY"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 3; - c.gridwidth = 1; - lootPane.add(ammoKey, c); - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 2; - lootPane.add(autoSab, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 2; - c.gridwidth = 1; - lootPane.add(new JLabel("KEY"), c); - c.weightx = 1; - c.gridy = y; - c.gridx = 3; - c.gridwidth = 1; - lootPane.add(ammoSabKey, c); - - ++y; - - c.weightx = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - - c.insets = sep; - lootPane.add(separator(), c); - c.insets = normal; - - ++y; - - c.weightx = 0; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 1; - lootPane.add(npcTableMapFilter, c); - c.weightx = 0; - c.gridy = y; - c.gridx = 1; - c.gridwidth = 3; - lootPane.add(npcTableNameFilter, c); - - ++y; - - c.weightx = 1; - c.weighty = 1; - c.gridy = y; - c.gridx = 0; - c.gridwidth = 4; - lootPane.add(new JScrollPane(npcTable), c); - //LOOT + mainPanel.setLayout(new MigLayout("ins 0, gap 0, wrap 1, fill", "[]", "[][grow]")); + mainPanel.add(new ConfigTitleBar(this, tabbedPane.getHeader(), configPicker, pluginTab, main), "grow, span"); + mainPanel.add(tabbedPane, "grow, span"); } - private void setComponentData() { - - for (String string : main.starManager.getAllMaps()) - workingMap.addItem(string); - - runConfig.addItem(1); - runConfig.addItem(2); - offensiveConfig.addItem(1); - offensiveConfig.addItem(2); - - reviveMethod.addItem("Base"); - reviveMethod.addItem("Portal"); - reviveMethod.addItem("Local"); - - reviveMethod.setSelectedIndex(Math.min(Math.max(config.REPAIR_LOCAL, 2), 0)); - - stayAwayFromEnemies.setSelected(config.STAY_AWAY_FROM_ENEMIES); - repairHp.setValue((int) (config.REPAIR_HP * 100)); - waitHp.setValue((int) (config.WAIT_HP * 100)); - - Map map = main.starManager.fromId(config.WORKING_MAP); - - if (map != null) - workingMap.setSelectedItem(map.name); - - runFromEnemies.setSelected(config.RUN_FROM_ENEMIES); - runFromEnemiesInSight.setSelected(config.RUN_FROM_ENEMIES_IN_SIGHT); - autoClock.setSelected(config.AUTO_CLOACK); - refreshTime.setText(String.valueOf(config.REFRESH_TIME / (1000 * 60))); - maxDeaths.setText(String.valueOf(config.MAX_DEATHS)); - autoSab.setSelected(config.AUTO_SAB); - autoCloackKey.setText(String.valueOf(config.AUTO_CLOACK_KEY)); - ammoKey.setText(String.valueOf(config.AMMO_KEY)); - ammoSabKey.setText(String.valueOf(config.AUTO_SAB_KEY)); - runFormation.setText(String.valueOf(config.RUN_FORMATION)); - offensiveFormation.setText(String.valueOf(config.OFFENSIVE_FORMATION)); - runConfig.setSelectedItem(config.RUN_CONFIG); - offensiveConfig.setSelectedItem(config.OFFENSIVE_CONFIG); - moduleCollector.setSelected(config.CURRENT_MODULE == 0); - moduleLoot.setSelected(config.CURRENT_MODULE == 1); - moduleLootNCollector.setSelected(config.CURRENT_MODULE == 2); - - npcTableMapFilter.addItem("ALL"); - - NpcTableModel npcModel = (NpcTableModel) npcTable.getModel(); - - for (java.util.Map.Entry entry : config.npcInfos.entrySet()) { - - String name = entry.getKey(); - NpcInfo info = entry.getValue(); - - npcModel.addEntry(name, info); - } - - BoxTableModel boxModel = (BoxTableModel) boxTable.getModel(); - - for (java.util.Map.Entry entry : config.boxInfos.entrySet()) { - - String name = entry.getKey(); - BoxInfo info = entry.getValue(); - - boxModel.addEntry(name, info); - } - - - boxTable.getColumnModel().getColumn(0).setPreferredWidth(200); - npcTable.getColumnModel().getColumn(0).setPreferredWidth(200); - - config.addedNpc.add(value -> npcModel.addEntry(value, config.npcInfos.get(value))); - config.addedBox.add(value -> boxModel.addEntry(value, config.boxInfos.get(value))); + public void setComponentData() { + advancedPane.setEditingConfig(main.config); + main.pluginHandler.addListener(advancedPane); + zones.setup(main); + configPicker.setup(main); + playerEditor.setup(main); + pluginDisplay.setup(main, pluginTab); } - private JSeparator separator() { - JSeparator separator = new JSeparator(); - - separator.setForeground(Color.gray); - - return separator; + void setCustomConfig(String name, Object config) { + advancedPane.setCustomConfig(name, config); } - private void setComponentEvent() { - - //GENERAL - - repairHp.addChangeListener(e -> config.REPAIR_HP = ((double) repairHp.getValue() / repairHp.getMaximum())); - - waitHp.addChangeListener(e -> config.WAIT_HP = ((double) waitHp.getValue() / waitHp.getMaximum())); - - workingMap.addItemListener(e -> config.WORKING_MAP = main.starManager.fromName((String) e.getItem()).id); - - reviveMethod.addItemListener(e -> config.REPAIR_LOCAL = reviveMethod.getSelectedIndex()); - - runConfig.addItemListener(e -> config.RUN_CONFIG = (int) e.getItem()); - - offensiveConfig.addItemListener(e -> config.OFFENSIVE_CONFIG = (int) e.getItem()); - - refreshTime.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - try { - int value = Integer.parseInt(refreshTime.getText()); - - if (value >= 0) { - config.REFRESH_TIME = value * 1000 * 60; - } else { - config.REFRESH_TIME = 0; - } - - } catch (NumberFormatException ignored) { - config.REFRESH_TIME = 0; - refreshTime.setText("0"); - } - - config.changed = true; - } - }); - - maxDeaths.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - try { - - config.MAX_DEATHS = Integer.parseInt(maxDeaths.getText()); - - } catch (NumberFormatException ignored) { - config.MAX_DEATHS = 10; - maxDeaths.setText("10"); - } - - config.changed = true; - } - }); - - offensiveFormation.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - offensiveFormation.setText(String.valueOf(e.getKeyChar())); - config.OFFENSIVE_FORMATION = e.getKeyChar(); - } - }); - - runFormation.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - runFormation.setText(String.valueOf(e.getKeyChar())); - config.RUN_FORMATION = e.getKeyChar(); - } - }); - - - moduleCollector.addItemListener(e -> { - - if (e.getStateChange() != ItemEvent.SELECTED) return; - - config.CURRENT_MODULE = 0; - main.setModule(new CollectorModule()); - }); - - moduleLoot.addItemListener(e -> { - - if (e.getStateChange() != ItemEvent.SELECTED) return; - - config.CURRENT_MODULE = 1; - main.setModule(new LootModule()); - }); - - moduleLootNCollector.addItemListener(e -> { - - if (e.getStateChange() != ItemEvent.SELECTED) return; - - config.CURRENT_MODULE = 2; - main.setModule(new LootNCollectorModule()); - }); - - //GENERAL - - //COLLECT - - stayAwayFromEnemies.addChangeListener(e -> config.STAY_AWAY_FROM_ENEMIES = stayAwayFromEnemies.isSelected()); - - autoClock.addItemListener(e -> config.AUTO_CLOACK = autoClock.isSelected()); - - autoCloackKey.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - autoCloackKey.setText(String.valueOf(e.getKeyChar())); - config.AUTO_CLOACK_KEY = e.getKeyChar(); - } - }); - - //COLLECT - - //LOOT - - runFromEnemies.addItemListener(e -> config.RUN_FROM_ENEMIES = runFromEnemies.isSelected()); - - runFromEnemiesInSight.addItemListener(e -> config.RUN_FROM_ENEMIES_IN_SIGHT = runFromEnemiesInSight.isSelected()); - - autoSab.addItemListener(e -> config.AUTO_SAB = autoSab.isSelected()); - - ammoSabKey.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - ammoSabKey.setText(String.valueOf(e.getKeyChar())); - config.AUTO_SAB_KEY = e.getKeyChar(); - } - }); - - ammoKey.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - ammoKey.setText(String.valueOf(e.getKeyChar())); - config.AMMO_KEY = e.getKeyChar(); - } - }); - - npcTableNameFilter.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - NpcTableModel npcModel = (NpcTableModel) npcTable.getModel(); - - npcModel.filterByName(npcTableNameFilter.getText()); - } - }); - - //LOOT + @Override + public void setVisible(boolean b) { + super.setVisible(b); + stateChange.send(b); } - private class BoxTableModel extends DefaultTableModel { - - public BoxTableModel() { - super(new Object[]{"Name", "Collect", "Wait"}, 0); - } - - @Override - public boolean isCellEditable(int row, int column) { - return column > 0; - } - - @Override - public Class getColumnClass(int column) { - return column == 1 ? Boolean.class : column == 2 ? Integer.class : super.getColumnClass(column); - } - - @Override - public void setValueAt(Object value, int row, int column) { - super.setValueAt(value, row, column); - - String name = (String) getValueAt(row, 0); - - BoxInfo info = config.boxInfos.get(name); - - switch (column) { - case 1: - info.collect = (boolean) value; - break; - case 2: - info.waitTime = (int) value; - break; - } - - config.changed = true; - } - - public void addEntry(String name, BoxInfo info) { - - boolean collect = info.collect; - int wait = info.waitTime; - - addRow(new Object[]{name, collect, wait}); - } + void addVisibilityListener(Consumer event) { + stateChange.add(event); } - private class NpcTableModel extends DefaultTableModel { - - public NpcTableModel() { - super(new Object[]{"Name", "Radius", "Priority", "Kill", "Last to kill"}, 0); - } - - public void addEntry(String name, NpcInfo info) { - - double radius = info.radius; - int priority = info.priority; - boolean kill = info.kill; - boolean last = info.killOnlyIfIsLast; - - addRow(new Object[]{name, radius, priority, kill, last}); - } - - public void filterByName(String filter) { - setNumRows(0); - - filter = filter.toLowerCase(); - - for (java.util.Map.Entry entry : config.npcInfos.entrySet()) { - - String name = entry.getKey(); - - if (name.toLowerCase().contains(filter)) { - addEntry(name, entry.getValue()); - } - - } - } - - @Override - public boolean isCellEditable(int row, int column) { - return column > 0; - } - - @Override - public Class getColumnClass(int column) { - return column == 3 || column == 4 ? Boolean.class : column == 1 ? Double.class : column == 2 ? Integer.class : super.getColumnClass(column); - } - - @Override - public void setValueAt(Object value, int row, int column) { - super.setValueAt(value, row, column); - - String name = (String) getValueAt(row, 0); - - NpcInfo info = config.npcInfos.get(name); - - switch (column) { - case 1: - info.radius = (double) value; - break; - case 2: - info.priority = (int) value; - break; - case 3: - info.kill = (boolean) value; - break; - case 4: - info.killOnlyIfIsLast = (boolean) value; - break; - } - - config.changed = true; - - } - } } diff --git a/src/main/java/com/github/manolo8/darkbot/gui/MainGui.java b/src/main/java/com/github/manolo8/darkbot/gui/MainGui.java index d2378f67..38281f6f 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/MainGui.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/MainGui.java @@ -1,146 +1,92 @@ package com.github.manolo8.darkbot.gui; import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.gui.components.ExitConfirmation; +import com.github.manolo8.darkbot.gui.titlebar.MainTitleBar; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.gui.utils.window.WindowUtils; +import net.miginfocom.swing.MigLayout; import javax.swing.*; import java.awt.*; -import java.awt.datatransfer.StringSelection; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowEvent; - -import static com.github.manolo8.darkbot.Main.API; +import java.util.function.Consumer; public class MainGui extends JFrame { private final Main main; private final ConfigGui configGui; + private final JPanel mainPanel = new JPanel(); + private ExitConfirmation exitConfirmation; private MapDrawer mapDrawer; - private boolean visible; - - private JButton toggleRunning; - private JButton toggleVisibility; - private JButton copySid; - private JButton openConfig; + public static final Image ICON = UIUtils.getImage("icon"); public MainGui(Main main) throws HeadlessException { super("DarkBot"); - this.main = main; - this.configGui = new ConfigGui(main); + configGui.setIconImage(ICON); + + this.main = main; - this.visible = true; + ToolTipManager.sharedInstance().setInitialDelay(350); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(800, 480); + setSize(640, 480); setLocationRelativeTo(null); - setVisible(true); + setIconImage(ICON); - initComponents(); setComponentPosition(); - setComponentEvent(); - } - private void initComponents() { - mapDrawer = new MapDrawer(main); - toggleRunning = new JButton("START"); - toggleVisibility = new JButton("HIDE"); - copySid = new JButton("SID"); - openConfig = new JButton("CONFIG"); + WindowUtils.setupUndecorated(this, mainPanel); + setVisible(true); + + setAlwaysOnTop(true); + toFront(); + requestFocus(); + setAlwaysOnTop(main.config.BOT_SETTINGS.DISPLAY.ALWAYS_ON_TOP); } private void setComponentPosition() { - getContentPane().setLayout(new GridBagLayout()); - - GridBagConstraints c = new GridBagConstraints(); - - c.fill = GridBagConstraints.BOTH; - - c.weightx = 1.0; - c.weighty = 1.0; - - add(mapDrawer, c); - - c.weighty = 0; - c.gridy = 1; + mainPanel.setLayout(new MigLayout("ins 0, gap 0, wrap 1, fill", "[]", "[][][grow]")); + mainPanel.add(new MainTitleBar(main, this), "grow, span"); + mainPanel.add(exitConfirmation = new ExitConfirmation(), "grow, span, hidemode 2"); + mainPanel.add(mapDrawer = new MapDrawer(main), "grow, span"); + } - Container container = new Container(); + public void addConfigVisibilityListener(Consumer listener) { + configGui.addVisibilityListener(listener); + } - container.setLayout(new GridLayout(1, 4)); + public void toggleConfig() { + boolean open = !configGui.isVisible(); + configGui.setVisible(open); + if (open) { + configGui.setAlwaysOnTop(this.isAlwaysOnTop()); + configGui.toFront(); + } + } - container.add(openConfig); - container.add(copySid); - container.add(toggleVisibility); - container.add(toggleRunning); + public void setCustomConfig(String name, Object config) { + configGui.setCustomConfig(name, config); + } - add(container, c); + public void updateConfiguration() { + mapDrawer.setup(main); + configGui.setComponentData(); } - private void setComponentEvent() { - - addWindowStateListener(e -> { - if (e.getNewState() == WindowEvent.WINDOW_CLOSING) { - main.saveConfig(); - } - }); - - toggleVisibility.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if (visible) { - toggleVisibility.setText("SHOW"); - API.setVisible(false); - visible = false; - } else { - toggleVisibility.setText("HIDE"); - API.setVisible(true); - visible = true; - } - } - }); - - toggleRunning.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - - boolean running = main.isRunning(); - - if (running) { - toggleRunning.setText("START"); - main.setRunning(false); - } else { - toggleRunning.setText("STOP"); - main.setRunning(true); - } - } - }); - - copySid.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - String value = main.statsManager.sid; - - if (value != null) { - StringSelection selection = new StringSelection(value); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection); - } - - } - }); - - openConfig.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - configGui.setVisible(!configGui.isVisible()); - } - }); + public void tryClose() { + if (main.config.BOT_SETTINGS.CONFIRM_EXIT) exitConfirmation.setVisible(true); + else { + System.out.println("Exit button pressed, exiting"); + System.exit(0); + } } public void tick() { - validate(); - repaint(); + mapDrawer.repaint(); } + } diff --git a/src/main/java/com/github/manolo8/darkbot/gui/MapDrawer.java b/src/main/java/com/github/manolo8/darkbot/gui/MapDrawer.java index 66249a4c..66a31be0 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/MapDrawer.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/MapDrawer.java @@ -2,70 +2,181 @@ import com.github.manolo8.darkbot.Main; import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.SafetyInfo; +import com.github.manolo8.darkbot.config.ZoneInfo; +import com.github.manolo8.darkbot.config.types.suppliers.DisplayFlag; +import com.github.manolo8.darkbot.core.entities.Barrier; +import com.github.manolo8.darkbot.core.entities.BasePoint; +import com.github.manolo8.darkbot.core.entities.BattleStation; import com.github.manolo8.darkbot.core.entities.Box; -import com.github.manolo8.darkbot.core.entities.*; -import com.github.manolo8.darkbot.core.manager.*; +import com.github.manolo8.darkbot.core.entities.Entity; +import com.github.manolo8.darkbot.core.entities.FakeNpc; +import com.github.manolo8.darkbot.core.entities.NoCloack; +import com.github.manolo8.darkbot.core.entities.Npc; +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.entities.Ship; +import com.github.manolo8.darkbot.core.entities.bases.BaseHeadquarters; +import com.github.manolo8.darkbot.core.entities.bases.BaseStation; +import com.github.manolo8.darkbot.core.entities.bases.BaseTurret; +import com.github.manolo8.darkbot.core.manager.GuiManager; +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.manager.MapManager; +import com.github.manolo8.darkbot.core.manager.PingManager; +import com.github.manolo8.darkbot.core.manager.StatsManager; +import com.github.manolo8.darkbot.core.objects.LocationInfo; +import com.github.manolo8.darkbot.core.objects.facades.BoosterProxy; +import com.github.manolo8.darkbot.core.objects.itf.HealthHolder; +import com.github.manolo8.darkbot.core.objects.group.Group; +import com.github.manolo8.darkbot.core.utils.Drive; import com.github.manolo8.darkbot.core.utils.Location; import com.github.manolo8.darkbot.core.utils.pathfinder.Area; -import com.github.manolo8.darkbot.core.utils.pathfinder.PathFinder; import com.github.manolo8.darkbot.core.utils.pathfinder.PathPoint; +import com.github.manolo8.darkbot.gui.trail.Line; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.Time; import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.font.TextAttribute; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static com.github.manolo8.darkbot.Main.API; public class MapDrawer extends JPanel { - private final DecimalFormat formatter; + private final DecimalFormat STAT_FORMAT = new DecimalFormat("###,###,###"); + private final NumberFormat HEALTH_FORMAT; + + { + DecimalFormatSymbols sym = new DecimalFormatSymbols(); + sym.setGroupingSeparator(' '); + HEALTH_FORMAT = new DecimalFormat("###,###,###", sym); + } private Color BACKGROUND = Color.decode("#263238"); private Color TEXT = Color.decode("#F2F2F2"); - private Color TEXT_DARK = Color.decode("#707070"); + protected Color TEXT_DARK = Color.decode("#BBBBBB"); private Color GOING = Color.decode("#8F9BFF"); private Color PORTALS = Color.decode("#AEAEAE"); - private Color OWNER = Color.decode("#FFF"); - private Color TRAIL = Color.decode("#E0E0E0"); - private Color BOXES = Color.decode("#C77800"); + private Color OWNER = Color.decode("#22CC22"); + private Color[] TRAIL = IntStream.rangeClosed(1, 255).mapToObj(i -> new Color(224, 224, 224, i)).toArray(Color[]::new); + private Color BOXES = Color.decode("#BBB830"); private Color ALLIES = Color.decode("#29B6F6"); private Color ENEMIES = Color.decode("#d50000"); - private Color NPCS = Color.decode("#9b0000"); + private Color NPCS = Color.decode("#AA4040"); + private Color TARGET = NPCS.darker(); private Color PET = Color.decode("#004c8c"); private Color PET_IN = Color.decode("#c56000"); private Color HEALTH = Color.decode("#388e3c"); - private Color HEALTH_BACK = Color.decode("#255d27"); + private Color NANO_HULL = Color.decode("#D0D024"); private Color SHIELD = Color.decode("#0288d1"); - private Color SHIELD_BACK = Color.decode("#015c8e"); - - private Font FONT_BIG = new Font("Consolas", Font.PLAIN, 32); + private Color METEROID = Color.decode("#AAAAAA"); + private Color PING = new Color(0, 255, 0, 32); + private Color PING_BORDER = new Color(0, 255, 0, 128); + private Color BARRIER = new Color(255, 255, 255, 32); + private Color BARRIER_BORDER = new Color(255, 255, 255, 128); + private Color NO_CLOACK = new Color(24, 160, 255, 32); + private Color PREFER = new Color(0, 255, 128, 32); + private Color AVOID = new Color(255, 0, 0, 32); + private Color SAFETY = new Color(16, 128, 255, 96); + + private Color BASES = new Color(0, 255, 128); + private Color BASE_SPOTS = new Color(0, 255, 128, 32); + private Color UNKNOWN = Color.decode("#7C05D1"); + private Color TEXTS_BACKGROUND = new Color(38, 50, 56, 128); + + private Color ACTION_BUTTON = new Color(255, 255, 255, 160); + private Color DARKEN_BACK = new Color(0, 0, 0, 96); + + protected Font FONT_BIG = new Font("Consolas", Font.PLAIN, 32); private Font FONT_MID = new Font(Font.SANS_SERIF, Font.PLAIN, 18); private Font FONT_SMALL = new Font("Consolas", Font.PLAIN, 12); + private Font FONT_TINY = new Font(Font.SANS_SERIF, Font.PLAIN, 9); - private double[][] lastPositions = new double[150][4]; + private final TreeMap positions = new TreeMap<>(); - private HeroManager hero; - private PathFinder pathFinder; + private Main main; + protected HeroManager hero; + private Drive drive; private MapManager mapManager; private GuiManager guiManager; private StatsManager statsManager; private PingManager pingManager; - private Config config; + protected Config config; - private java.util.List portals; - private java.util.List npcs; - private java.util.List boxes; - private java.util.List ships; - private java.util.List battleStations; + private List portals; + private List npcs; + private FakeNpc fakeNpc; + private List boxes; + private List ships; + private List battleStations; + private List basePoints; - private RenderingHints hints; + private final RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - private Location last; - private int current; + private Location last = new Location(0, 0); + + protected boolean hovering; + protected int width, height, mid; + + public MapDrawer() { + addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent evt) { + hovering = true; + repaint(); + } + + public void mouseExited(MouseEvent evt) { + hovering = false; + repaint(); + } + }); + } public MapDrawer(Main main) { + this(); + setBorder(UIUtils.getBorder()); + setup(main); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (main.config.BOT_SETTINGS.MAP_START_STOP && SwingUtilities.isLeftMouseButton(e)) { + main.setRunning(!main.isRunning()); + repaint(); + return; + } + hero.drive.move(undoTranslateX(e.getX()), undoTranslateY(e.getY())); + } + }); + addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + if (main.config.BOT_SETTINGS.MAP_START_STOP && SwingUtilities.isLeftMouseButton(e)) return; + hero.drive.move(undoTranslateX(e.getX()), undoTranslateY(e.getY())); + } + }); + } + + public void setup(Main main) { + this.main = main; this.hero = main.hero; - this.pathFinder = main.hero.drive.pathFinder; + this.drive = main.hero.drive; this.mapManager = main.mapManager; this.guiManager = main.guiManager; this.statsManager = main.statsManager; @@ -74,35 +185,74 @@ public MapDrawer(Main main) { this.portals = main.mapManager.entities.portals; this.npcs = main.mapManager.entities.npcs; + this.fakeNpc = main.mapManager.entities.fakeNpc; this.boxes = main.mapManager.entities.boxes; this.ships = main.mapManager.entities.ships; this.battleStations = main.mapManager.entities.battleStations; + this.basePoints = main.mapManager.entities.basePoints; + } - this.formatter = new DecimalFormat("###,###,###"); - this.last = new Location(0, 0); + @Override + protected void paintComponent(Graphics g) { + if (main == null) return; + Graphics2D g2 = setupDraw(g); - this.hints = new RenderingHints( - RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON - ); - addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - hero.drive.move(undoTranslateX(e.getX()), undoTranslateY(e.getY())); + synchronized (Main.UPDATE_LOCKER) { + drawZones(g2); + if (hasFlag(DisplayFlag.ZONES)) drawCustomZones(g2); + drawInfos(g2); + drawHealth(g2); + drawTrail(g2); + drawStaticEntities(g2); + drawDynamicEntities(g2); + drawHero(g2); + } + + if (config.BOT_SETTINGS.DEV_STUFF) { + g2.setFont(FONT_TINY); + g2.setColor(TEXT_DARK); + synchronized (Main.UPDATE_LOCKER) { + List entities = mapManager.entities.allEntities.stream().flatMap(Collection::stream) + .filter(e -> e.id > 150_000_000 && e.id < 160_000_000) + .filter(e -> e.locationInfo.isLoaded()) + .collect(Collectors.toList()); + + g2.setColor(TEXTS_BACKGROUND); + for (Entity e : entities) { + Location loc = e.locationInfo.now; + int strWidth = g2.getFontMetrics().stringWidth(e.toString()); + g2.fillRect(translateX(loc.x) - (strWidth >> 1), translateY(loc.y) - 7, strWidth, 8); + } + g2.setColor(TEXT); + g2.setFont(FONT_TINY); + for (Entity e : entities) { + Location loc = e.locationInfo.now; + drawString(g2, e.toString(), translateX(loc.x), translateY(loc.y), Align.MID); + } } - }); - } + } - @Override - protected void paintComponent(Graphics g) { + synchronized (Main.UPDATE_LOCKER) { + if (!drawGroup(g2)) drawBoosters(g2); + } - int height = getHeight(); - int width = getWidth(); + if (hasFlag(DisplayFlag.STATS_AREA)) + drawBackgroundedText(g2, Align.LEFT, + "cre/h " + STAT_FORMAT.format(statsManager.earnedCredits()), + "uri/h " + STAT_FORMAT.format(statsManager.earnedUridium()), + "exp/h " + STAT_FORMAT.format(statsManager.earnedExperience()), + "hon/h " + STAT_FORMAT.format(statsManager.earnedHonor()), + "cargo " + statsManager.deposit + "/" + statsManager.depositTotal, + "death " + guiManager.deaths + '/' + config.GENERAL.SAFETY.MAX_DEATHS); - int fontWidth; + if (hovering && main.config.BOT_SETTINGS.MAP_START_STOP) drawActionButton(g2); + } - int mid = width / 2; + protected Graphics2D setupDraw(Graphics g) { + height = getHeight(); + width = getWidth(); + mid = width / 2; g.setColor(BACKGROUND); g.fillRect(0, 0, width, height); @@ -110,315 +260,456 @@ protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHints(hints); + return g2; + } - g2.setColor(TEXT_DARK); - - String ping = pingManager.ping + " ms"; + protected void drawZones(Graphics2D g2) { + for (Barrier barrier : mapManager.entities.barriers) { + if (!barrier.use()) continue; + Area area = barrier.getZone(); + g2.setColor(this.BARRIER); + g2.fillRect( + translateX(area.minX), translateY(area.minY), + translateX(area.maxX - area.minX), translateY(area.maxY - area.minY)); + g2.setColor(BARRIER_BORDER); + g2.drawRect( + translateX(area.minX), translateY(area.minY), + translateX(area.maxX - area.minX), translateY(area.maxY - area.minY)); + } - fontWidth = g2.getFontMetrics().stringWidth(ping); - g2.drawString(ping, width - fontWidth - 10, 20); + g2.setColor(this.NO_CLOACK); + for (NoCloack noCloack : mapManager.entities.noCloack) { + Area area = noCloack.getZone(); + g2.fillRect( + translateX(area.minX), translateY(area.minY), + translateX(area.maxX - area.minX), translateY(area.maxY - area.minY)); + } + } - String running = "RUNNING " + statsManager.runningTimeStr(); + protected void drawCustomZones(Graphics2D g2) { + g2.setColor(PREFER); + drawCustomZone(g2, config.PREFERRED.get(hero.map.id)); + if (config.GENERAL.ROAMING.SEQUENTIAL) drawCustomZonePath(g2, config.PREFERRED.get(hero.map.id)); + g2.setColor(AVOID); + drawCustomZone(g2, config.AVOIDED.get(hero.map.id)); + g2.setColor(SAFETY); + for (SafetyInfo safety : config.SAFETY.get(hero.map.id)) { + if (safety.runMode == SafetyInfo.RunMode.NEVER + || safety.entity == null || safety.entity.removed) continue; + drawSafeZone(g2, safety); + } + } - fontWidth = g2.getFontMetrics().stringWidth(running); - g2.drawString(running, mid - (fontWidth / 2), (height / 2) + 35); - g2.setFont(FONT_BIG); - fontWidth = g2.getFontMetrics().stringWidth(hero.map.name); - g2.drawString(hero.map.name, mid - (fontWidth / 2), (height / 2) - 5); + private void drawInfos(Graphics2D g2) { + g2.setColor(TEXT_DARK); + String status = I18n.get( + (main.isRunning() ? "gui.map.running" : "gui.map.waiting"), + Time.toString(statsManager.runningTime())); + drawString(g2, status, mid, height / 2 + 35, Align.MID); - g2.setColor(TEXT); g2.setFont(FONT_SMALL); + String info = I18n.get("gui.map.info", + Main.VERSION.toString(), + (main.isRunning() || !config.MISCELLANEOUS.RESET_REFRESH ? + Time.toString(System.currentTimeMillis() - main.lastRefresh) : "00"), + Time.toString(config.MISCELLANEOUS.REFRESH_TIME * 60 * 1000)); + drawString(g2, info, 5, 12, Align.LEFT); + if (main.module != null) { + drawString(g2, main.tickingModule ? main.module.status() : main.module.stoppedStatus(), 5, 26, Align.LEFT); + } - int center = (height / 2) - 30; - - g2.drawString("cre/h " + formatter.format(statsManager.earnedCredits()), 0, center); - g2.drawString("uri/h " + formatter.format(statsManager.earnedUridium()), 0, center + 15); - g2.drawString("exp/h " + formatter.format(statsManager.earnedExperience()), 0, center + 30); - g2.drawString("hon/h " + formatter.format(statsManager.earnedHonor()), 0, center + 45); - g2.drawString("death " + guiManager.deaths + '/' + config.MAX_DEATHS, 0, center + 60); + drawString(g2, String.format("%.1ftick %dFPS %dMB %dms ping", main.avgTick, + main.facadeManager.stats.getFps(), API.getMemoryUsage(), pingManager.ping), width - 5, 12, Align.RIGHT); + drawString(g2, "SID: " + main.backpage.sidStatus(), width - 5, 26, Align.RIGHT); - g.setColor(GOING); + drawMap(g2); + } - PathPoint begin = new PathPoint((int) hero.locationInfo.now.x, (int) hero.locationInfo.now.y); - for (PathPoint path : pathFinder.path()) { + protected void drawMap(Graphics2D g2) { + g2.setColor(TEXT_DARK); + g2.setFont(FONT_BIG); + drawString(g2, hero.map.name, mid, (height / 2) - 5, Align.MID); + } - g.drawLine( - translateX(path.x), - translateY(path.y), - translateX(begin.x), - translateY(begin.y) - ); + private void drawHealth(Graphics2D g2) { + g2.setColor(TEXT); + g2.setFont(FONT_MID); + if (hasFlag(DisplayFlag.HERO_NAME)) + drawString(g2, hero.playerInfo.username, 10 + (mid - 20) / 2, height - 40, Align.MID); + drawHealth(g2, hero.health, 10, this.getHeight() - 34, mid - 20, 12); + if (hero.target != null && !hero.target.removed) { + if (hero.target instanceof Npc || hero.target.playerInfo.isEnemy()) g2.setColor(this.ENEMIES); + else g2.setColor(this.ALLIES); + g2.setFont(FONT_MID); + String name = hero.target.playerInfo.username; + drawString(g2, name, mid + 10 + (mid - 20) / 2, height - 40, Align.MID); - begin = path; + drawHealth(g2, hero.target.health, mid + 10, height - 34, mid - 20, 12); } + } + private void drawTrail(Graphics2D g2) { + Location heroLocation = hero.locationInfo.now; - synchronized (Main.UPDATE_LOCKER) { - - g2.setFont(FONT_SMALL); - g2.setColor(PORTALS); - for (Portal portal : portals) { - - Location loc = portal.locationInfo.now; + double distance = last.distance(heroLocation); - g2.drawOval( - translateX(loc.x) - 5, - translateY(loc.y) - 5, - 10, - 10 - ); + if (distance > 500) { + last = hero.locationInfo.now.copy(); + } else if (distance > 100) { + positions.put(System.currentTimeMillis(), new Line(last, last = heroLocation.copy())); + } + positions.headMap(System.currentTimeMillis() - config.BOT_SETTINGS.DISPLAY.TRAIL_LENGTH * 1000).clear(); + + if (positions.isEmpty()) return; + + g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); + List> paths = Line.getSmoothedPaths(positions.values()); + double max = paths.stream().mapToInt(Collection::size).sum() / 255d, curr = 0; + for (List points : paths) { + Location last = null; + for (Location point : points) { + g2.setColor(TRAIL[(int) (curr++ / max)]); + if (last != null) drawLine(g2, last, point); + last = point; } + } + g2.setStroke(new BasicStroke()); + } - for (Barrier barrier : mapManager.entities.barriers) { + protected void drawStaticEntities(Graphics2D g2) { + g2.setColor(PORTALS); + for (Portal portal : portals) { + Location loc = portal.locationInfo.now; + g2.drawOval(translateX(loc.x) - 6, translateY(loc.y) - 6, 11, 11); + } - //4= - Area area = barrier.getArea(); + for (BattleStation station : this.battleStations) { + if (station.hullId == 0) g2.setColor(this.METEROID); + else if (station.info.isEnemy()) g2.setColor(this.ENEMIES); + else g2.setColor(this.ALLIES); - g.drawRect( - translateX(area.minX), - translateY(area.minY), - translateX(area.maxX - area.minX), - translateY(area.maxY - area.minY) - ); - } + Location loc = station.locationInfo.now; + if (station.hullId >= 0 && station.hullId < 255) + g2.fillOval(translateX(loc.x) - 5, translateY(loc.y) - 4, 11, 9); + else drawEntity(g2, loc, false); + } - g2.setColor(BOXES); - for (Box box : boxes) { + for (BasePoint base : this.basePoints) { + Location loc = base.locationInfo.now; + if (base instanceof BaseTurret) { + g2.setColor(this.BASES); + g2.fillOval(this.translateX(loc.x) - 1, this.translateY(loc.y) - 1, 2, 2); + } else { + g2.setColor(this.BASE_SPOTS); + int radius = base instanceof BaseHeadquarters ? 3500 : + base instanceof BaseStation ? 3000 : 1000, half = radius / 2; + g2.fillOval(translateX(loc.x - half), translateY(loc.y - half), translateX(radius), translateY(radius)); + } + } + } - Location loc = box.locationInfo.now; + private void drawDynamicEntities(Graphics2D g2) { + g2.setColor(BOXES); + for (Box box : boxes) drawEntity(g2, box.locationInfo.now, box.boxInfo.collect); - int x = translateX(loc.x) - 1; - int y = translateY(loc.y) - 1; + if (config.BOT_SETTINGS.DEV_STUFF) { + g2.setColor(GOING); + for (Npc npc : npcs) drawLine(g2, npc.locationInfo, npc.shipInfo.destination); + for (Ship ship : ships) drawLine(g2, ship.locationInfo, ship.shipInfo.destination); + } - if (box.boxInfo.collect) { - g2.fillRect(x, y, 4, 4); - } else { - g2.drawRect(x, y, 3, 3); - } + g2.setColor(NPCS); + for (Npc npc : npcs) drawEntity(g2, npc.locationInfo.now, npc.npcInfo.kill); + if (fakeNpc.isPingAlive()) { + Location loc = fakeNpc.locationInfo.now; + g2.setColor(PING); + g2.fillOval(translateX(loc.x) - 7, translateY(loc.y) - 7, 15, 15); + g2.setColor(PING_BORDER); + g2.drawOval(translateX(loc.x) - 7, translateY(loc.y) - 7, 15, 15); + } - } + for (Ship ship : ships) { + Location loc = ship.locationInfo.now; + g2.setColor(ship.playerInfo.isEnemy() ? ENEMIES : ALLIES); + drawEntity(g2, ship.locationInfo.now, false); + if (hasFlag(DisplayFlag.USERNAMES)) + drawString(g2, ship.playerInfo.username, translateX(loc.x), translateY(loc.y) - 5, Align.MID); + } - g2.setColor(NPCS); - for (Npc npc : npcs) { + if (hero.target != null && !hero.target.removed) { + g2.setColor(GOING); + drawLine(g2, hero.target.locationInfo, hero.target.shipInfo.destination); + g2.setColor(TARGET); + drawEntity(g2, hero.target.locationInfo.now, true); + } - Location loc = npc.locationInfo.now; + if (!config.BOT_SETTINGS.DEV_STUFF) return; - int x = translateX(loc.x) - 1; - int y = translateY(loc.y) - 1; + g2.setColor(UNKNOWN); + for (Entity entity : mapManager.entities.unknown) { + drawEntity(g2, entity.locationInfo.now, false); + } - if (npc.npcInfo.kill) { - g2.fillRect(x, y, 4, 4); - } else { - g2.drawRect(x, y, 3, 3); - } - } + for (PathPoint point : hero.drive.pathFinder.points) { + g2.fillRect(translateX(point.x), translateY(point.y), 2, 2); + } + } - for (BattleStation station : battleStations) { + private void drawHero(Graphics2D g2) { + g2.setColor(TEXT); + g2.setFont(FONT_SMALL); + drawString(g2, hero.config + "C", 12, height - 12, Align.LEFT); - if (station.info.isEnemy()) { - g2.setColor(ENEMIES); - } else { - g2.setColor(ALLIES); - } + if (!hero.locationInfo.isLoaded()) return; - Location loc = station.locationInfo.now; + g2.setColor(GOING); + PathPoint begin = new PathPoint((int) hero.locationInfo.now.x, (int) hero.locationInfo.now.y); + for (PathPoint path : drive.paths) { + g2.drawLine(translateX(begin.x), translateY(begin.y), + translateX(path.x), translateY((begin = path).y)); + } - int x = translateX(loc.x); - int y = translateY(loc.y); + g2.setColor(OWNER); - g2.fillRect( - x - 15, - y - 10, - 30, - 20 - ); + Location loc = hero.locationInfo.now; + g2.fillOval(translateX(loc.x) - 3, translateY(loc.y) - 3, 7, 7); - g2.setColor(Color.BLUE); - g2.fillOval(x - 9, y - 5, 6, 6); - g2.fillOval(x + 3, y - 5, 6, 6); + g2.setColor(BARRIER_BORDER); + g2.drawRect(translateX(mapManager.boundX), translateY(mapManager.boundY), + translateX(mapManager.boundMaxX - mapManager.boundX), + translateY(mapManager.boundMaxY - mapManager.boundY)); - g2.setColor(Color.RED); - g2.fillRect( - x - 13, - y + 3, - 26, - 6 - ); + if (hero.pet.removed || !hero.pet.locationInfo.isLoaded()) return; + loc = hero.pet.locationInfo.now; + int x = translateX(loc.x), + y = translateY(loc.y); - g2.setColor(Color.WHITE); + g2.setColor(PET); + g2.fillRect(x - 3, y - 3, 6, 6); - for (int i = 0; i < 4; i++) { - g2.fillRect( - x - 12 + (i * 7), - y + 4, - 3, - 4 - ); - } - } + g2.setColor(PET_IN); + g2.fillRect(x - 2, y - 2, 4, 4); + } - for (Ship ship : ships) { + private boolean drawGroup(Graphics2D g2) { + if (!hasFlag(DisplayFlag.GROUP_AREA)) return false; + + Group group = main.guiManager.group.group; + if (group == null || !group.isValid()) return false; + boolean hideNames = !hasFlag(DisplayFlag.GROUP_NAMES); + drawBackgrounded(g2, 28, Align.RIGHT, + (x, y, w, member) -> { + Font font = FONT_SMALL; + Color color = TEXT; + + Map attrs = new HashMap<>(); + attrs.put(TextAttribute.WEIGHT, member.isLeader ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR); + attrs.put(TextAttribute.STRIKETHROUGH, member.isDead ? TextAttribute.STRIKETHROUGH_ON : false); + attrs.put(TextAttribute.UNDERLINE, member.isLocked ? TextAttribute.UNDERLINE_ON : -1); + if (member.isCloacked) color = color.darker(); + + g2.setFont(font.deriveFont(attrs)); + g2.setColor(color); + g2.drawString(member.getDisplayText(hideNames), x, y + 14); + + drawHealth(g2, member.memberInfo, x, y + 18, w / 2 - 3, 4); + if (member.targetInfo.shipType != 0) + drawHealth(g2, member.targetInfo, x + (w / 2) + 3, y + 18, w / 2 - 3, 4); + }, + member -> Math.min(g2.getFontMetrics().stringWidth(member.getDisplayText(hideNames)), 200), + group.members); + return true; + } - Location loc = ship.locationInfo.now; + private void drawBoosters(Graphics2D g2) { + if (!hasFlag(DisplayFlag.BOOSTER_AREA)) return; - if (ship.playerInfo.isEnemy()) { - g2.setColor(ENEMIES); - } else { - g2.setColor(ALLIES); - } + Stream boosters = main.facadeManager.booster.boosters.stream().filter(b -> b.amount > 0); + if (hasFlag(DisplayFlag.SORT_BOOSTERS)) + boosters = boosters.sorted(Comparator.comparingDouble(b -> -b.cd)); - g2.drawRect( - translateX(loc.x) - 1, - translateY(loc.y) - 1, - 3, - 3 - ); + drawBackgrounded(g2, 15, Align.RIGHT, + (x, y, w, booster) -> { + g2.setColor(booster.getColor()); + g2.drawString(booster.toSimpleString(), x, y + 14); + }, + b -> g2.getFontMetrics().stringWidth(b.toSimpleString()), + boosters.collect(Collectors.toList())); + } - } + private void drawActionButton(Graphics2D g2) { + g2.setColor(this.DARKEN_BACK); + g2.fillRect(0, 0, this.getWidth(), this.getHeight()); + g2.setColor(this.ACTION_BUTTON); + int height2 = this.getHeight() / 2, height3 = this.getHeight() / 3, + width3 = this.getWidth() / 3, width9 = this.getWidth() / 9; + if (this.main.isRunning()) { + g2.fillRect(width3, height3, width9, height3); // Two vertical parallel lines + g2.fillRect((width3 * 2) - width9, height3, width9, height3); + } else { // A "play" triangle + g2.fillPolygon(new int[]{width3, width3 * 2, width3}, + new int[]{height3, height2, height3 * 2}, 3); } + } - Location heroLocation = hero.locationInfo.now; - - double distance = last.distance(heroLocation); - - if (distance > 500) { - last = hero.locationInfo.now.copy(); - } else if (distance > 60) { + private void drawBackgroundedText(Graphics2D g2, Align align, String... texts) { + this.drawBackgrounded(g2, 15, align, + (x, y, h, str) -> g2.drawString(str, x, y + 14), + g2.getFontMetrics()::stringWidth, + Arrays.asList(texts)); + } - int index = current % lastPositions.length; + private void drawBackgrounded(Graphics2D g2, int lineHeight, Align align, + Renderer renderer, + ToIntFunction widthGetter, + Collection toRender) { + if (toRender.size() == 0) return; + g2.setFont(FONT_SMALL); - lastPositions[index][0] = heroLocation.x; - lastPositions[index][1] = heroLocation.y; - lastPositions[index][2] = last.x; - lastPositions[index][3] = last.y; - current++; + int width = toRender.stream().mapToInt(widthGetter).max().orElse(0) + 8; + int height = toRender.size() * lineHeight + 4; + int top = getHeight() / 2 - height / 2; + int left = align == Align.RIGHT ? getWidth() - width : 0; - last = heroLocation.copy(); + g2.setColor(TEXTS_BACKGROUND); + g2.fillRect(left, top, width, height); + g2.setColor(TEXT); + for (T render : toRender) { + renderer.render(left + 4, top, width - 8, render); + top += lineHeight; } + } - g2.setColor(TRAIL); - - int x, y; + @FunctionalInterface + private interface Renderer { + void render(int x, int y, int w, T object); + } - for (double[] lastPosition : lastPositions) { - g2.drawLine( - translateX(lastPosition[0]), - translateY(lastPosition[1]), - translateX(lastPosition[2]), - translateY(lastPosition[3]) - ); + protected void drawCustomZone(Graphics2D g2, ZoneInfo zoneInfo) { + if (zoneInfo == null) return; + for (int x = 0; x < zoneInfo.resolution; x++) { + for (int y = 0; y < zoneInfo.resolution; y++) { + if (!zoneInfo.get(x, y)) continue; + int startX = gridToMapX(x), startY = gridToMapY(y); + g2.fillRect(startX, startY, gridToMapX(x + 1) - startX, gridToMapY(y + 1) - startY); + } } + } - g2.setColor(OWNER); - - g2.fillOval( - translateX(heroLocation.x) - 3, - translateY(heroLocation.y) - 3, - 7, - 7 - ); - - if (!hero.pet.removed) { - - Location loc = hero.pet.locationInfo.now; + protected void drawCustomZonePath(Graphics2D g2, ZoneInfo zoneInfo) { + if (zoneInfo == null) return; + List zones = zoneInfo.getSortedZones(); + for (int i = 0; i < zones.size(); i++) { + Location loc1 = zones.get(i).innerPoint(0.5, 0.5, MapManager.internalWidth, MapManager.internalHeight); + Location loc2 = zones.get((i + 1) % zones.size()).innerPoint(0.5, 0.5, MapManager.internalWidth, MapManager.internalHeight); + drawLine(g2, loc1, loc2); + } + } - x = translateX(loc.x); - y = translateY(loc.y); + protected void drawSafeZone(Graphics2D g2, SafetyInfo safetyInfo) { + if (safetyInfo == null) return; + int radius = safetyInfo.radius(); + g2.fillOval(translateX(safetyInfo.x - radius), translateY(safetyInfo.y - radius), + translateX(safetyInfo.diameter()), translateY(safetyInfo.diameter())); + } - g2.setColor(PET); - g2.fillRect( - x - 3, - y - 3, - 6, - 6 - ); + private void drawHealth(Graphics2D g2, HealthHolder health, int x, int y, int width, int height) { + g2.setFont(FONT_SMALL); - g2.setColor(PET_IN); - g2.fillRect( - x - 2, - y - 2, - 4, - 4 - ); - } + boolean displayAmount = height >= 8 && hasFlag(DisplayFlag.HP_SHIELD_NUM); + int margin = height < 8 ? 2 : 0; - { - g2.setColor(TEXT); - g2.setFont(FONT_MID); - fontWidth = g2.getFontMetrics().stringWidth(hero.playerInfo.username); - g2.drawString(hero.playerInfo.username, 10 + (mid - 20) / 2 - fontWidth / 2, height - 40); + int totalMaxHealth = health.getMaxHp() + health.getHull(); + int hullWidth = totalMaxHealth == 0 ? 0 : (health.getHull() * width / totalMaxHealth); - String health = formatter.format(hero.health.hp); - String shield = formatter.format(hero.health.shield); + g2.setColor(HEALTH.darker()); + g2.fillRect(x, y, width, height); + g2.setColor(HEALTH); + g2.fillRect(x, y, hullWidth + (int) (health.hpPercent() * (width - hullWidth)), height); + g2.setColor(NANO_HULL); + g2.fillRect(x, y, hullWidth, height); - g2.setColor(HEALTH_BACK); - g2.fillRect(10, height - 34, mid - 20, 12); - g2.setColor(SHIELD_BACK); - g2.fillRect(10, height - 22, mid - 20, 12); + g2.setColor(TEXT); + if (displayAmount) + drawString(g2, HEALTH_FORMAT.format(health.getHull() + health.getHp()) + "/" + + HEALTH_FORMAT.format(totalMaxHealth), x + width / 2, y + height - 2, Align.MID); - g2.setColor(HEALTH); - g2.fillRect(10, height - 34, (int) (hero.health.hpPercent() * mid) - 20, 12); + if (health.getMaxShield() != 0) { + g2.setColor(SHIELD.darker()); + g2.fillRect(x, y + height + margin, width, height); g2.setColor(SHIELD); - g2.fillRect(10, height - 22, (int) (hero.health.shieldPercent() * mid) - 20, 12); - - g2.setFont(FONT_SMALL); + g2.fillRect(x, y + height + margin, (int) (health.shieldPercent() * width), height); g2.setColor(TEXT); - fontWidth = g2.getFontMetrics().stringWidth(health); - g2.drawString(health, 10 + (mid - 20) / 2 - fontWidth / 2, height - 24); - fontWidth = g2.getFontMetrics().stringWidth(shield); - g2.drawString(shield, 10 + (mid - 20) / 2 - fontWidth / 2, height - 12); + if (displayAmount) + drawString(g2, HEALTH_FORMAT.format(health.getShield()) + "/" + + HEALTH_FORMAT.format(health.getMaxShield()), x + width / 2, y + height + height - 2, Align.MID); + } + } + protected enum Align { + LEFT, MID, RIGHT + } + + protected void drawString(Graphics2D g2, String str, int x, int y, Align align) { + if (str == null || str.isEmpty()) return; + if (align != Align.LEFT) { + int strWidth = g2.getFontMetrics().stringWidth(str); + x -= strWidth >> (align == Align.MID ? 1 : 0); } + g2.drawString(str, x, y); + } - if (hero.target != null && !hero.target.removed) { - Ship ship = hero.target; + private void drawLine(Graphics2D g2, LocationInfo a, LocationInfo b) { + if (!a.isLoaded() || !b.isLoaded()) return; + drawLine(g2, a.now, b.now); + } - if (ship instanceof Npc) { - g2.setColor(TEXT); - g2.setFont(FONT_MID); - String name = ((Npc) ship).playerInfo.username; + private void drawLine(Graphics2D g2, Location a, Location b) { + g2.drawLine(translateX(a.x), translateY(a.y), translateX(b.x), translateY(b.y)); + } - fontWidth = g2.getFontMetrics().stringWidth(name); - g2.drawString(name, 10 + mid + (mid - 20) / 2 - fontWidth / 2, height - 40); - } + private void drawEntity(Graphics2D g2, Location loc, boolean fill) { + int x = this.translateX(loc.x) - 1; + int y = this.translateY(loc.y) - 1; + if (fill) g2.fillRect(x, y, 4, 4); + else g2.drawRect(x, y, 3, 3); + } + + private boolean hasFlag(DisplayFlag df) { + return config.BOT_SETTINGS.DISPLAY.TOGGLE.contains(df); + } - String health = formatter.format(ship.health.hp); - String shield = formatter.format(ship.health.shield); + protected int translateX(double x) { + return (int) ((x / (double) MapManager.internalWidth) * getWidth()); + } - g2.setColor(HEALTH_BACK); - g2.fillRect(mid + 10, height - 34, mid - 20, 12); - g2.setColor(SHIELD_BACK); - g2.fillRect(mid + 10, height - 22, mid - 20, 12); + protected int translateY(double y) { + return (int) ((y / (double) MapManager.internalHeight) * getHeight()); + } - g2.setColor(HEALTH); - g2.fillRect(mid + 10, height - 34, (int) (ship.health.hpPercent() * mid) - 20, 12); - g2.setColor(SHIELD); - g2.fillRect(mid + 10, height - 22, (int) (ship.health.shieldPercent() * mid) - 20, 12); + protected double undoTranslateX(double x) { + return ((x / (double) getWidth()) * MapManager.internalWidth); + } - g2.setFont(FONT_SMALL); - g2.setColor(TEXT); - fontWidth = g2.getFontMetrics().stringWidth(health); - g2.drawString(health, 10 + mid + (mid - 20) / 2 - fontWidth / 2, height - 24); - fontWidth = g2.getFontMetrics().stringWidth(shield); - g2.drawString(shield, 10 + mid + (mid - 20) / 2 - fontWidth / 2, height - 12); - } + protected double undoTranslateY(double y) { + return ((y / (double) getHeight()) * MapManager.internalHeight); } - private int translateX(double x) { - return (int) ((x / (double) MapManager.internalWidth) * getWidth()); + protected int gridToMapX(int x) { + return x * width / config.BOT_SETTINGS.ZONE_RESOLUTION; } - private double undoTranslateX(double x) { - return ((x / getWidth()) * MapManager.internalWidth); + protected int gridToMapY(int y) { + return y * height / config.BOT_SETTINGS.ZONE_RESOLUTION; } - private double undoTranslateY(double y) { - return ((y / getHeight()) * MapManager.internalHeight); + protected int mapToGridX(int x) { + return (x + 1) * config.BOT_SETTINGS.ZONE_RESOLUTION / width; } - private int translateY(double y) { - return (int) ((y / (double) MapManager.internalHeight) * getHeight()); + protected int mapToGridY(int y) { + return (y + 1) * config.BOT_SETTINGS.ZONE_RESOLUTION / height; } } diff --git a/src/main/java/com/github/manolo8/darkbot/gui/components/ApiSettings.java b/src/main/java/com/github/manolo8/darkbot/gui/components/ApiSettings.java new file mode 100644 index 00000000..2bb44e69 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/components/ApiSettings.java @@ -0,0 +1,80 @@ +package com.github.manolo8.darkbot.gui.components; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.gui.utils.SpinnerNumberMinMaxFix; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +public class ApiSettings extends JDialog { + + public ApiSettings(Main main, Point p) { + + add(new ApiSettingsPanel(main.config.BOT_SETTINGS.DISPLAY)); + + setUndecorated(true); + setAlwaysOnTop(true); + requestFocusInWindow(); + + pack(); + setLocation(p); + setVisible(true); + + addWindowListener(new WindowAdapter() { + @Override + public void windowDeactivated(WindowEvent e) { + setVisible(false); + } + }); + } + + private class ApiSettingsPanel extends JPanel { + + private final Config.BotSettings.Display display; + private final JSpinner w, h; + + public ApiSettingsPanel(Config.BotSettings.Display display) { + super(new MigLayout("wrap 6")); + this.display = display; + + setBorder(UIUtils.getBorder()); + + add(w = createSpinner(display.width, 480, 4096), "span 2, align left"); + add(new JLabel("x"), "span 2, align right"); + add(h = createSpinner(display.height, 320, 2160), "span 2, align right"); + + JButton apply = new JButton("Apply"); + apply.addActionListener(a -> updateSize()); + add(apply, "span 3, grow"); + ApiSettings.super.getRootPane().setDefaultButton(apply); + + JButton def = new JButton("Default"); + def.setDefaultCapable(false); + def.addActionListener(a -> { + w.setValue(1280); + h.setValue(800); + updateSize(); + }); + add(def, "span 3, grow"); + add(UIUtils.setRed(new JLabel(I18n.get("bot.issue.resolution")), true), "span"); + } + + private JSpinner createSpinner(int val, int min, int max) { + JSpinner sp = new JSpinner(new SpinnerNumberMinMaxFix(val, min, max, 100)); + ((JSpinner.NumberEditor) sp.getEditor()).getTextField().setColumns(3); + return sp; + } + + private void updateSize() { + Main.API.setSize(display.width = (int) w.getValue(), display.height = (int) h.getValue()); + ConfigEntity.changed(); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/components/ExitConfirmation.java b/src/main/java/com/github/manolo8/darkbot/gui/components/ExitConfirmation.java new file mode 100644 index 00000000..27b7fc5c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/components/ExitConfirmation.java @@ -0,0 +1,58 @@ +package com.github.manolo8.darkbot.gui.components; + +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class ExitConfirmation extends JPanel { + + public ExitConfirmation() { + super(new MigLayout("ins 0, gap 0, fill", "[grow][][]", "[]")); + + add(new Question(I18n.get("gui.exit")), "grow"); + add(new YesButton()); + add(new CancelButton()); + setVisible(false); + setToolTipText(I18n.get("gui.exit.desc")); + } + + private static class Question extends JLabel { + private Question(String text) { + super(text); + setBorder(UIUtils.getBorderWithInsets(true)); + } + } + + private static class YesButton extends MainButton { + + private YesButton() { + super(I18n.get("gui.exit.yes_button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Exit confirmation button pressed, exiting"); + System.exit(0); + } + } + + private class CancelButton extends MainButton { + + private CancelButton() { + super(I18n.get("gui.exit.cancel_button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + ExitConfirmation.this.setVisible(false); + } + + + + } + + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/components/MainButton.java b/src/main/java/com/github/manolo8/darkbot/gui/components/MainButton.java new file mode 100644 index 00000000..edeb7719 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/components/MainButton.java @@ -0,0 +1,53 @@ +package com.github.manolo8.darkbot.gui.components; + +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class MainButton extends JButton implements ActionListener { + + protected Color actionColor; // Currently useless. + protected boolean defaultInsets; + + public MainButton(String text) { + this(null, text); + } + + public MainButton(Icon icon) { + this(icon, null); + } + + public MainButton(Icon icon, boolean defaultInsets) { + this(icon); + this.defaultInsets = defaultInsets; + } + + protected MainButton(Icon icon, String text) { + this(icon, text, null); + } + + protected MainButton(Icon icon, String text, String description) { + super(text, icon); + putClientProperty("JButton.buttonType", "square"); + + addActionListener(this); + if (description != null && !description.isEmpty()) setToolTipText(description); + } + + @Override + public void actionPerformed(ActionEvent e) {} + + @Override + public Insets getInsets() { + if (defaultInsets) return super.getInsets(); + return UIUtils.getInsetConfig(getText() != null && !getText().isEmpty()); + } + + @Override + public boolean isDefaultButton() { + return false; // Avoid painting as blue & focused + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/components/MainToggleButton.java b/src/main/java/com/github/manolo8/darkbot/gui/components/MainToggleButton.java new file mode 100644 index 00000000..f4ed0ad8 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/components/MainToggleButton.java @@ -0,0 +1,40 @@ +package com.github.manolo8.darkbot.gui.components; + +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class MainToggleButton extends JToggleButton implements ActionListener { + + public MainToggleButton(String text) { + this(null, text); + } + + public MainToggleButton(Icon icon) { + this(icon, null); + } + + protected MainToggleButton(Icon icon, String text) { + this(icon, text, null); + } + + protected MainToggleButton(Icon icon, String text, String description) { + super(text, icon); + putClientProperty("JButton.buttonType", "square"); + + addActionListener(this); + if (description != null && !description.isEmpty()) setToolTipText(description); + } + + @Override + public void actionPerformed(ActionEvent e) {} + + @Override + public Insets getInsets() { + return UIUtils.getInsetConfig(getText() != null && !getText().isEmpty()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/components/TabbedPane.java b/src/main/java/com/github/manolo8/darkbot/gui/components/TabbedPane.java new file mode 100644 index 00000000..4a7415e6 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/components/TabbedPane.java @@ -0,0 +1,70 @@ +package com.github.manolo8.darkbot.gui.components; + +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; + +public class TabbedPane extends JPanel { + + private Tab current; + + private List header = new ArrayList<>(); + + public List getHeader() { + return header; + } + + public TabbedPane() { + super(new MigLayout("ins 0, gap 0, fill", "[grow]", "[grow]")); + setBorder(UIUtils.getPartialBorder(0, 1, 1, 1)); + } + + public void addTab(Icon icon, String key, JComponent component) { + addTab(new Tab(icon, key, component), true); + } + + public MainButton addHiddenTab(Icon icon, String name, JComponent component) { + return addTab(new Tab(icon, name, component), false); + } + + private Tab addTab(Tab tab, boolean inHeader) { + if (inHeader) header.add(tab); + if (current == null) selectTab(tab); + return tab; + } + + + private void selectTab(Tab tab) { + Tab old = current; + current = tab; + if (old != null) { + remove(old.component); + } + add(tab.component, "grow"); + revalidate(); + repaint(); + } + + private class Tab extends MainButton { + + private final JComponent component; + + private Tab(Icon icon, String key, JComponent component) { + super(icon, I18n.getOrDefault(key, null), + key == null ? null : I18n.getOrDefault(key + ".desc", null)); + this.component = component; + } + + @Override + public void actionPerformed(ActionEvent e) { + selectTab(this); + } + + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/login/LoginForm.java b/src/main/java/com/github/manolo8/darkbot/gui/login/LoginForm.java new file mode 100644 index 00000000..1b23e148 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/login/LoginForm.java @@ -0,0 +1,110 @@ +package com.github.manolo8.darkbot.gui.login; + +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.login.LoginData; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.util.List; + +public class LoginForm extends JPanel { + + private final JTabbedPane tabbedPane = new JTabbedPane(); + + private final JLabel infoLb = new JLabel(""); + private final JButton loginBtn = new JButton("Log in"); + + private final LoginData loginData = new LoginData(); + + public LoginForm() { + super(new MigLayout("wrap 2, ins 0", "[]10px:push[]", "[]8px[]")); + tabbedPane.addTab("User & Pass", new UserLogin()); + tabbedPane.addTab("SID login", new SidLogin()); + SavedLogins saved = new SavedLogins(this); + tabbedPane.addTab("Saved", saved); + tabbedPane.setEnabledAt(2, saved.isLoaded()); + if (saved.getLogins() > 0) tabbedPane.setSelectedComponent(saved); + + loginBtn.addActionListener(ac -> startLogin()); + + add(tabbedPane, "span 2, growx, height 112px!"); + add(infoLb, "gapleft 8px, grow 0"); + add(loginBtn, "gapright 8px"); + } + + public void setDialog(JDialog dialog) { + dialog.getRootPane().setDefaultButton(loginBtn); + } + + public LoginData getResult() { + return loginData; + } + + public void setInfoText(Message val) { + infoLb.setText(val.text); + infoLb.setToolTipText(val.description); + UIUtils.setRed(infoLb, val.error); + } + + public static class Message { + private final boolean error; + private final String text; + private final String description; + + public Message(boolean error, String text, String description) { + this.error = error; + this.text = text; + this.description = description; + } + } + + private boolean canLogIn = true; + protected synchronized void startLogin() { + if (!canLogIn) return; + loginBtn.setEnabled(canLogIn = false); + setInfoText(new Message(false, "Logging in (1/2)", null)); + new LoginTask().execute(); + } + + protected synchronized void endLogin() { + loginBtn.setEnabled(canLogIn = true); + } + + private class LoginTask extends SwingWorker { + private boolean failed = false; + + @Override + protected void process(List chunks) { + setInfoText(chunks.get(chunks.size() - 1)); + } + + @Override + protected void done() { + if (!failed) SwingUtilities.getWindowAncestor(LoginForm.this).setVisible(false); + endLogin(); + } + + @Override + protected LoginData doInBackground() { + try { + publish(new Message(false, "Logging in (1/2)", null)); + Message msg = ((LoginScreen) tabbedPane.getSelectedComponent()).tryLogin(loginData); + if (msg != null) { + publish(msg); + failed = true; + return null; + } + publish(new Message(false, "Loading spacemap (2/2)", null)); + LoginUtils.findPreloader(loginData); + return loginData; + } catch (Exception e) { + publish(new Message(true, "Failed to login!", IssueHandler.createDescription(e))); + failed = true; + } + return null; + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/login/LoginScreen.java b/src/main/java/com/github/manolo8/darkbot/gui/login/LoginScreen.java new file mode 100644 index 00000000..9af6c1ea --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/login/LoginScreen.java @@ -0,0 +1,12 @@ +package com.github.manolo8.darkbot.gui.login; + +import com.github.manolo8.darkbot.utils.login.LoginData; + +public interface LoginScreen { + /** + * Try to perform login in this login screen + * @param login The login data to update + * @return null if it went right, message to display otherwise + */ + LoginForm.Message tryLogin(LoginData login); +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/login/SavedLogins.java b/src/main/java/com/github/manolo8/darkbot/gui/login/SavedLogins.java new file mode 100644 index 00000000..8efe5c8f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/login/SavedLogins.java @@ -0,0 +1,186 @@ +package com.github.manolo8.darkbot.gui.login; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.login.Credentials; +import com.github.manolo8.darkbot.utils.login.LoginData; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class SavedLogins extends JPanel implements LoginScreen { + public LoginForm loginForm; + + private char[] password; + private boolean loaded = false; + private final Credentials credentials = LoginUtils.loadCredentials(); + + private final DefaultListModel model = new DefaultListModel<>(); + private final JList users = new JList<>(model); + + public SavedLogins(LoginForm loginForm) { + super(new MigLayout("ins 0, gap 0, wrap 2", "[26px!][grow]", "[26px!][26px!][26px!]")); + this.loginForm = loginForm; + + users.setLayoutOrientation(JList.HORIZONTAL_WRAP); + users.setVisibleRowCount(-1); + users.setFixedCellHeight(19); + + try { + if (!credentials.isEmpty()) { + password = requestMasterPassword(); + if (password == null) { + loginForm.setInfoText(new LoginForm.Message(true, "Didn't load credentials", + "Master password was cancelled. Saved credentials aren't available")); + } else { + credentials.decrypt(password); + loaded = true; + } + } else { + loaded = true; + } + } catch (Exception e) { + loginForm.setInfoText(new LoginForm.Message(true, "Couldn't load credentials", IssueHandler.createDescription(e))); + } + + users.setEnabled(loaded); + + add(new AddLogin(), "grow"); + add(new JScrollPane(users), "grow, span 1 3"); + add(new EditLogin(), "grow"); + add(new RemoveLogin(), "grow"); + + updateList(); + users.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() < 2) return; + int idx = users.locationToIndex(e.getPoint()); + if (idx == -1) return; + if (users.getCellBounds(idx, idx).contains(e.getPoint())) loginForm.startLogin(); + } + }); + } + + public boolean isLoaded() { + return loaded; + } + + public int getLogins() { + return loaded ? model.getSize() : 0; + } + + private void updateList() { + if (!loaded) return; // Prevent list even trying + + model.clear(); + for (Credentials.User user : credentials.getUsers()) model.addElement(user); + + if (!credentials.getUsers().isEmpty() && password == null) + password = createMasterPassword(); + + try { + LoginUtils.saveCredentials(credentials, password); + } catch (Exception e) { + loginForm.setInfoText(new LoginForm.Message(true, "Couldn't save credentials", IssueHandler.createDescription(e))); + } + } + + private char[] requestMasterPassword() { + if (ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.DISABLE_MASTER_PASSWORD) return new char[]{}; + + JPasswordField pass = new JPasswordField(10); + + JOptionPane pane = new JOptionPane(new Object[]{"Input your master password:", pass}, + JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + Popups.showMessageSync("Darkbot Master password", pane); + Object result = pane.getValue(); + + if (result instanceof Integer && (Integer) result == JOptionPane.YES_OPTION) return pass.getPassword(); + return null; + } + + private char[] createMasterPassword() { + if (ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.DISABLE_MASTER_PASSWORD) return new char[]{}; + JPasswordField pass = new JPasswordField(10); + + JOptionPane.showMessageDialog(this, new Object[]{ + "Master password for darkbot to encrypt your credentials.\n" + + "You can use a blank (empty) password:", pass}, + "Darkbot Master password", JOptionPane.PLAIN_MESSAGE); + + return pass.getPassword(); + } + + @Override + public LoginForm.Message tryLogin(LoginData login) { + Credentials.User user = users.getSelectedValue(); + if (user == null) return new LoginForm.Message(true, "No user selected", "Select the user to login with"); + + login.setCredentials(user.u, user.p); + LoginUtils.usernameLogin(login); + return null; + } + + private class AddLogin extends MainButton { + public AddLogin() { + super(UIUtils.getIcon("add"), true); + setEnabled(loaded); + } + + @Override + public void actionPerformed(ActionEvent e) { + UserLogin newUser = new UserLogin(); + int result = JOptionPane.showConfirmDialog(this, newUser, "Add new login", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (result != JOptionPane.YES_OPTION || + newUser.getUsername().isEmpty() || newUser.getPassword().isEmpty()) return; + + credentials.getUsers().add(new Credentials.User(newUser.getUsername(), newUser.getPassword())); + updateList(); + } + } + + private class EditLogin extends MainButton { + public EditLogin() { + super(UIUtils.getIcon("edit"), true); + setEnabled(loaded); + } + + @Override + public void actionPerformed(ActionEvent e) { + Credentials.User user = users.getSelectedValue(); + if (user == null) return; + UserLogin editor = new UserLogin(user.u, user.p); + int result = JOptionPane.showConfirmDialog(this, editor, "Edit login", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (result != JOptionPane.YES_OPTION || + editor.getUsername().isEmpty() || editor.getPassword().isEmpty()) return; + + user.u = editor.getUsername(); + user.p = editor.getPassword(); + + updateList(); + } + } + + private class RemoveLogin extends MainButton { + public RemoveLogin() { + super(UIUtils.getIcon("remove"), true); + setEnabled(loaded); + } + + @Override + public void actionPerformed(ActionEvent e) { + credentials.getUsers().removeAll(users.getSelectedValuesList()); + updateList(); + } + + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/login/SidLogin.java b/src/main/java/com/github/manolo8/darkbot/gui/login/SidLogin.java new file mode 100644 index 00000000..5a8c2539 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/login/SidLogin.java @@ -0,0 +1,24 @@ +package com.github.manolo8.darkbot.gui.login; + +import com.github.manolo8.darkbot.utils.login.LoginData; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; + +public class SidLogin extends JPanel implements LoginScreen { + private JTextField sv = new JTextField(4), sid = new JTextField(16); + + public SidLogin() { + super(new MigLayout("wrap 2", "[]8px:push[]", "push[][]push")); + add(new JLabel("Server")); + add(sv); + add(new JLabel("SID")); + add(sid); + } + + @Override + public LoginForm.Message tryLogin(LoginData login) { + login.setSid(sid.getText(), sv.getText() + ".darkorbit.com"); + return null; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/login/UserLogin.java b/src/main/java/com/github/manolo8/darkbot/gui/login/UserLogin.java new file mode 100644 index 00000000..16489545 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/login/UserLogin.java @@ -0,0 +1,41 @@ +package com.github.manolo8.darkbot.gui.login; + +import com.github.manolo8.darkbot.utils.login.LoginData; +import com.github.manolo8.darkbot.utils.login.LoginUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; + +public class UserLogin extends JPanel implements LoginScreen { + private JTextField username = new JTextField(16), password = new JPasswordField(16); + + public UserLogin() { + super(new MigLayout("wrap 2", "[]8px:push[]", "push[][]push")); + add(new JLabel("Username")); + add(username); + add(new JLabel("Password")); + add(password); + } + + public UserLogin(String username, String password) { + this(); + this.username.setText(username); + this.password.setText(password); + } + + public String getUsername() { + return username.getText(); + } + + public String getPassword() { + return password.getText(); + } + + @Override + public LoginForm.Message tryLogin(LoginData login) { + login.setCredentials(username.getText(), password.getText()); + LoginUtils.usernameLogin(login); + return null; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerEditor.java new file mode 100644 index 00000000..fb4a9b91 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerEditor.java @@ -0,0 +1,178 @@ +package com.github.manolo8.darkbot.gui.players; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.PlayerInfo; +import com.github.manolo8.darkbot.config.PlayerTag; +import com.github.manolo8.darkbot.config.UnresolvedPlayer; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.gui.utils.SearchField; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +public class PlayerEditor extends JPanel { + private JList playerInfoList; + private DefaultListModel playersModel; + private SearchField sf; + private PlayerTagEditor tagEditor; + protected Main main; + + public PlayerEditor() { + super(new MigLayout("ins 0, gap 0, wrap 5, fill", "[][][][grow][]", "[][grow,fill]")); + + add(new AddPlayer(), "grow"); + add(new AddId(), "grow"); + add(new RemovePlayers(), "grow"); + add(sf = new SearchField(this::refreshList), "grow"); + add(tagEditor = new PlayerTagEditor(this), "grow"); + + playerInfoList = new JList<>(playersModel = new DefaultListModel<>()); + playerInfoList.setCellRenderer(new PlayerRenderer()); + + add(new JScrollPane(playerInfoList, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), "span, grow"); + } + + public void setup(Main main) { + this.main = main; + + tagEditor.setup(main); + + refreshList(); + main.config.PLAYER_UPDATED.add(i -> SwingUtilities.invokeLater(this::refreshList)); + } + + public void refreshList() { + refreshList(sf.getText()); + } + + public void refreshList(String filter) { + if (main == null) return; + String query = filter == null ? null : filter.toLowerCase(Locale.ROOT); + playersModel.clear(); + main.config.PLAYER_INFOS + .values() + .stream() + .filter(pi -> pi.filter(query)) + .sorted(Comparator.comparing(pi -> pi.username)) + .forEach(playersModel::addElement); + } + + public void addTagToPlayers(PlayerTag tag) { + List players = playerInfoList.getSelectedValuesList(); + + if (tag == null) { + tag = PlayerTagUtils.createTag(this); + if (tag == null) return; + main.config.PLAYER_TAGS.add(tag); + } else if (players.isEmpty()) { + Popups.showMessageAsync(I18n.get("players.select_players.warn.title"), I18n.get("players.select_players.warn.add"), JOptionPane.INFORMATION_MESSAGE); + return; + } + + for (PlayerInfo p : players) p.setTag(tag, null); + main.config.changed = true; + playerInfoList.updateUI(); + } + + public void removeTagFromPlayers(PlayerTag tag) { + if (tag == null) return; + List players = playerInfoList.getSelectedValuesList(); + + if (players.isEmpty()) { + Popups.showMessageAsync(I18n.get("players.select_players.warn.title"), I18n.get("players.select_players.warn.remove"), JOptionPane.INFORMATION_MESSAGE); + return; + } + for (PlayerInfo p : players) p.removeTag(tag); + main.config.changed = true; + playerInfoList.updateUI(); + } + + public void deleteTag(PlayerTag tag) { + if (tag == null) return; + int result = JOptionPane.showConfirmDialog(this, + I18n.get("players.delete_tag.confirm.msg", tag.name), + I18n.get("players.delete_tag.confirm.title"), + JOptionPane.YES_NO_OPTION); + if (result != JOptionPane.YES_OPTION) return; + + main.config.PLAYER_TAGS.remove(tag); + for (PlayerInfo p : main.config.PLAYER_INFOS.values()) { + p.removeTag(tag); + } + main.config.changed = true; + playerInfoList.updateUI(); + } + + public void removePlayers() { + List players = playerInfoList.getSelectedValuesList(); + if (players.isEmpty()) { + Popups.showMessageAsync(I18n.get("players.select_players.warn.title"), I18n.get("players.select_players.warn.remove_player"), JOptionPane.INFORMATION_MESSAGE); + return; + } + + int result = JOptionPane.showConfirmDialog(this, + I18n.get("players.remove_player.msg", players.size()), + I18n.get("players.remove_player.title"), + JOptionPane.YES_NO_OPTION); + if (result != JOptionPane.YES_OPTION) return; + + for (PlayerInfo player : players) { + main.config.PLAYER_INFOS.remove(player.userId); + playersModel.removeElement(player); + } + main.config.changed = true; + } + + public class AddPlayer extends MainButton { + public AddPlayer() { + super(UIUtils.getIcon("add"), I18n.get("players.add_player.by_name")); + } + + @Override + public void actionPerformed(ActionEvent e) { + String name = JOptionPane.showInputDialog(this, I18n.get("players.add_player.username"), + I18n.get("players.add_player"), JOptionPane.QUESTION_MESSAGE); + if (name == null) return; + main.config.UNRESOLVED.add(new UnresolvedPlayer(name)); + } + } + + public class AddId extends MainButton { + public AddId() { + super(UIUtils.getIcon("add"), I18n.get("players.add_player.by_id")); + } + + @Override + public void actionPerformed(ActionEvent e) { + String id = JOptionPane.showInputDialog(this, I18n.get("players.add_player.user_id"), + I18n.get("players.add_player"), JOptionPane.QUESTION_MESSAGE); + if (id == null) return; + try { + main.config.UNRESOLVED.add(new UnresolvedPlayer(Integer.parseInt(id.trim()))); + } catch (NumberFormatException ex) { + Popups.showMessageAsync(I18n.get("players.add_player.invalid_id"), + I18n.get("players.add_player.invalid_id.no_number", id), JOptionPane.ERROR_MESSAGE); + } + } + } + + public class RemovePlayers extends MainButton { + public RemovePlayers() { + super(UIUtils.getIcon("remove")); + } + + @Override + public void actionPerformed(ActionEvent e) { + removePlayers(); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerRenderer.java b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerRenderer.java new file mode 100644 index 00000000..e1dee5be --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerRenderer.java @@ -0,0 +1,110 @@ +package com.github.manolo8.darkbot.gui.players; + +import com.github.manolo8.darkbot.config.PlayerInfo; +import com.github.manolo8.darkbot.config.PlayerTag; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +public class PlayerRenderer extends JPanel implements ListCellRenderer { + + private JLabel playername = new JLabel(); + private JLabel id = new JLabel(); + + private Map tagCache = new HashMap<>(); + + public PlayerRenderer() { + super(new MigLayout("ins 4px 0px 4px 5px, fill, h 28px!", "[50px!]8px![120px!]8px:push[]8px!", "[]")); + id.setFont(id.getFont().deriveFont(9f)); + id.setHorizontalAlignment(SwingConstants.RIGHT); + id.setVerticalAlignment(SwingConstants.BOTTOM); + } + + @Override + public Component getListCellRendererComponent(JList list, PlayerInfo value, + int index, boolean isSelected, boolean cellHasFocus) { + removeAll(); + + setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); + setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + + this.playername.setText(value.username); + this.id.setText(String.valueOf(value.userId)); + + add(id, "grow"); + add(playername, "grow"); + for (PlayerTag tag : value.getTags()) + add(tagCache.computeIfAbsent(tag, Tag::new), "grow"); + + return this; + } + + private static class Tag extends JLabel { + private static final int ALPHA = 96; + private static final Border MARGIN = new EmptyBorder(1, 5, 1, 5); + + public Tag(PlayerTag tag) { + super(tag.name); + setOpaque(true); + setBorder(new CompoundBorder(BorderFactory.createLineBorder(tag.color), MARGIN)); + setBackground(UIUtils.blendColor(tag.color, ALPHA)); + } + } + + /** + * Functions overridden for performance reasons: + */ + @Override + public boolean isOpaque() { + return true; + } + + /* + // We can't override these or the tags start bugging due to layout manager. + @Override + public void validate() {} + @Override + public void invalidate() {}*/ + @Override + public void repaint() {} + @Override + public void revalidate() {} + @Override + public void repaint(long tm, int x, int y, int width, int height) {} + @Override + public void repaint(Rectangle r) {} + @Override + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + // Strings get interned... + if (propertyName == "text" + || ((propertyName == "font" || propertyName == "foreground") + && oldValue != newValue + && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { + + super.firePropertyChange(propertyName, oldValue, newValue); + } + } + @Override + public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {} + @Override + public void firePropertyChange(String propertyName, char oldValue, char newValue) {} + @Override + public void firePropertyChange(String propertyName, short oldValue, short newValue) {} + @Override + public void firePropertyChange(String propertyName, int oldValue, int newValue) {} + @Override + public void firePropertyChange(String propertyName, long oldValue, long newValue) {} + @Override + public void firePropertyChange(String propertyName, float oldValue, float newValue) {} + @Override + public void firePropertyChange(String propertyName, double oldValue, double newValue) {} + @Override + public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {} +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerTagEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerTagEditor.java new file mode 100644 index 00000000..3e9bf0a8 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerTagEditor.java @@ -0,0 +1,91 @@ +package com.github.manolo8.darkbot.gui.players; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.PlayerTag; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.utils.PopupMenuListenerAdapter; +import com.github.manolo8.darkbot.gui.utils.SimpleMouseListener; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.util.function.Consumer; + +public class PlayerTagEditor extends JPanel { + private PlayerEditor editor; + + private TagButton latestClicked; + private TagPopup tags; + + public PlayerTagEditor(PlayerEditor editor) { + super(new MigLayout("ins 0, gap 0", "[][][]")); + this.editor = editor; + + add(new TagButton(UIUtils.getIcon("add"), null, editor::addTagToPlayers, "Add new tag", true, false)); + add(new TagButton(UIUtils.getIcon("remove"), null, editor::removeTagFromPlayers, null, false, false)); + add(new TagButton(UIUtils.getIcon("close"), "Manage tags", editor::deleteTag, null, false, true)); + tags = new TagPopup(tag -> latestClicked.action.accept(tag)); + tags.addPopupMenuListener(new PopupMenuListenerAdapter() { + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + latestClicked.keepClosed = System.currentTimeMillis() + 100; + } + }); + } + + void setup(Main main) { + tags.setTags(main.config.PLAYER_TAGS); + } + + public void showPopup(TagButton clicked) { + latestClicked = clicked; + if (clicked.keepClosed > System.currentTimeMillis()) { + clicked.keepClosed = 0; + return; + } + + if (editor.main != null && editor.main.config.PLAYER_TAGS.isEmpty()) { + editor.addTagToPlayers(null); + return; + } + + tags.show(clicked, 0, getHeight() - 1, clicked.nullTag); + } + + private class TagButton extends MainButton implements SimpleMouseListener { + private long keepClosed; + private Consumer action; + private String nullTag; + + public TagButton(Icon icon, String text, + Consumer action, + String nullTag, + boolean leftBorder, + boolean rightBorder) { + super(icon, text); + this.action = action; + this.nullTag = nullTag; + setBorder(UIUtils.getPartialBorder(1, leftBorder ? 1 : 0, 1, rightBorder ? 1 : 0)); + addMouseListener(this); + } + + @Override + public void mousePressed(MouseEvent e) { + if (keepClosed > System.currentTimeMillis()) keepClosed = Long.MAX_VALUE; + } + + @Override + public void mouseExited(MouseEvent e) { + keepClosed = 0; + } + + @Override + public void actionPerformed(ActionEvent e) { + showPopup(this); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerTagUtils.java b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerTagUtils.java new file mode 100644 index 00000000..fd03480d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/players/PlayerTagUtils.java @@ -0,0 +1,67 @@ +package com.github.manolo8.darkbot.gui.players; + +import com.formdev.flatlaf.ui.FlatSliderUI; +import com.formdev.flatlaf.util.UIScale; +import com.github.manolo8.darkbot.config.PlayerTag; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.util.stream.IntStream; + +public class PlayerTagUtils { + + public static PlayerTag createTag(Component parent) { + JPanel panel = new JPanel(new MigLayout("ins 0, wrap 2", "[]3px[grow]")); + JTextField name = new JTextField(20); + JSlider color = new JSlider(0, 255); + color.setUI(new PlayerTagUtils.ColoredSliderUI(0.6f, 0.6f)); + + panel.add(new JLabel("Tag ")); + panel.add(name, "grow"); + panel.add(new JLabel("Color")); + panel.add(color, "grow"); + + JButton createTag = new JButton("Create Tag"); + createTag.addActionListener(event -> SwingUtilities.getWindowAncestor(panel).setVisible(false)); + + JOptionPane pane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, new Object[]{createTag}, createTag); + JDialog dialog = pane.createDialog(parent, "Create Player tag"); + + pane.selectInitialValue(); + dialog.setVisible(true); + dialog.dispose(); + + if (pane.getValue() == null || name.getText().trim().isEmpty()) return null; + + return PlayerTag.getTag(name.getText(), Color.getHSBColor(color.getValue() / 255f, 0.6f, 0.6f)); + } + + public static class ColoredSliderUI extends FlatSliderUI { + private static final int TRACK_HEIGHT = 8; + private static final int THUMB_WIDTH = 20; + private Color[] gradient; + public ColoredSliderUI(float sat, float val) { + gradient = IntStream.rangeClosed(0, 255).mapToObj(i -> Color.getHSBColor(i / 255f, sat, val)).toArray(Color[]::new); + } + + protected Dimension getThumbSize() { + return new Dimension(UIScale.scale(THUMB_WIDTH), UIScale.scale(THUMB_WIDTH)); + } + + public void paintTrack(Graphics g2d) { + int drawY = trackRect.y + (trackRect.height / 2) - TRACK_HEIGHT / 2; + for (int i = 0; i < trackRect.width; i++) { + g2d.setColor(gradient[(int) (i * 255f / trackRect.width)]); + g2d.drawRect(trackRect.x + i, drawY, 1, TRACK_HEIGHT); + } + } + + @Override + public void paintThumb(Graphics g) { + g.setColor(gradient[slider.getValue()]); + g.fillOval( thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height ); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/players/TagPopup.java b/src/main/java/com/github/manolo8/darkbot/gui/players/TagPopup.java new file mode 100644 index 00000000..700bac52 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/players/TagPopup.java @@ -0,0 +1,78 @@ +package com.github.manolo8.darkbot.gui.players; + +import com.github.manolo8.darkbot.config.PlayerTag; +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class TagPopup extends JPopupMenu { + + private Collection playerTags; + private Consumer callback; + + private Map tagCache = new HashMap<>(); + private Map nullTagsCache = new HashMap<>(); + + public TagPopup(Collection playerTags, Consumer callback) { + super("Player tags"); + this.playerTags = playerTags; + this.callback = callback; + } + + public TagPopup(Consumer callback) { + super("Player tags"); + this.playerTags = Collections.emptyList(); + this.callback = callback; + } + + public void setTags(Collection tags) { + this.playerTags = tags; + this.tagCache.clear(); + this.nullTagsCache.clear(); + } + + public void show(Component invoker, int x, int y, String nullTag) { + removeAll(); + for (PlayerTag tag : playerTags) { + add(tagCache.computeIfAbsent(tag, TagEntry::new)); + } + + if (nullTag != null) { + addSeparator(); + add(nullTagsCache.computeIfAbsent(nullTag, TagEntry::new)); + } + show(invoker, x, y); + } + + private class TagEntry extends JMenuItem implements ActionListener { + private static final int ALPHA = 96; + private PlayerTag tag = null; + + TagEntry(String name) { + super(name); + setOpaque(true); + this.addActionListener(this); + } + + TagEntry(PlayerTag tag) { + this(tag.name); + this.tag = tag; + setBackground(UIUtils.blendColor(tag.color, ALPHA)); + } + + @Override + public void actionPerformed(ActionEvent e) { + callback.accept(tag); + } + + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/ExceptionCard.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/ExceptionCard.java new file mode 100644 index 00000000..e866db7d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/ExceptionCard.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.extensions.plugins.PluginLoadingException; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; + +public class ExceptionCard extends JPanel { + + private static final Border ERROR_BORDER = BorderFactory.createLineBorder(UIUtils.RED); + private static int ALPHA = 96 << 24; + private static final Color ERROR_COLOR = new Color(UIUtils.RED.getRGB() + ALPHA, true); + + public ExceptionCard(PluginLoadingException exception) { + super(new MigLayout("", "[grow]10px[]", "")); + setBorder(ERROR_BORDER); + setBackground(ERROR_COLOR); + + if (exception.getPlugin() != null) add(new JLabel(exception.getPlugin().getName())); + JLabel label = new JLabel(exception.getMessage()); + if (exception.getCause() != null) { + label.setToolTipText(exception.getCause().toString()); + } + add(label); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureCheckbox.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureCheckbox.java new file mode 100644 index 00000000..f4b23398 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureCheckbox.java @@ -0,0 +1,28 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; + +import javax.swing.*; + +class FeatureCheckbox extends JCheckBox { + + FeatureCheckbox(FeatureDefinition feature) { + setSelected(feature.isEnabled()); + + setText(feature.getName()); + if (!feature.getDescription().isEmpty()) + setToolTipText(feature.getDescription()); + setOpaque(false); + addActionListener(a -> feature.setStatus(isSelected())); + + updateIssues(feature.getIssues()); + feature.getIssues().addListener(this::updateIssues); + } + + private void updateIssues(IssueHandler issueList) { + setEnabled(issueList.canLoad()); + setSelected(isSelected() && issueList.canLoad()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureConfigButton.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureConfigButton.java new file mode 100644 index 00000000..26a2fb0e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureConfigButton.java @@ -0,0 +1,63 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.core.itf.Configurable; +import com.github.manolo8.darkbot.core.itf.InstructionProvider; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.components.MainToggleButton; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class FeatureConfigButton extends MainToggleButton { + + private Config config; + private FeatureDefinition feature; + + FeatureConfigButton(Config config, FeatureDefinition feature) { + super(UIUtils.getIcon("config")); + setDisabledIcon(UIUtils.getIcon("config_unloaded")); + this.config = config; + this.feature = feature; + updateStatus(feature); + feature.addStatusListener(this::updateStatus); + } + + private void updateStatus(FeatureDefinition feature) { + boolean enabled = feature.canLoad() && feature.getInstance() != null; + + this.setEnabled(enabled); + setToolTipText(enabled ? I18n.get("plugins.config_button.enabled.desc") : I18n.get("plugins.config_button.disabled.desc")); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (feature.getInstance() == null) { + Popups.showMessageAsync(I18n.get("plugins.config_button.popup"), + I18n.get("plugins.config_button.popup.desc"), JOptionPane.INFORMATION_MESSAGE); + } else { + Object paneMessage = new AdvancedConfig(this.config.CUSTOM_CONFIGS.get(feature.getId())); + + JComponent instructions = getInstructions(); + if (instructions != null) paneMessage = new Object[]{instructions, paneMessage}; + + JOptionPane options = new JOptionPane(paneMessage, + JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}, null); + options.setBorder(BorderFactory.createEmptyBorder(0, 0, -4, 0)); + Popups.showMessageSync(feature.getName(), options); + } + } + + private JComponent getInstructions() { + Object instance = feature.getInstance(); + if (instance instanceof InstructionProvider) { + return ((InstructionProvider) instance).beforeConfig(); + } + return null; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureTypeButton.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureTypeButton.java new file mode 100644 index 00000000..9e828e6d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/FeatureTypeButton.java @@ -0,0 +1,64 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.core.itf.Behaviour; +import com.github.manolo8.darkbot.core.itf.InstructionProvider; +import com.github.manolo8.darkbot.core.itf.Module; +import com.github.manolo8.darkbot.core.itf.Task; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.gui.components.MainButton; + +import java.awt.event.ActionEvent; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class FeatureTypeButton extends MainButton { + + private static final Map, String> FEATURE_TYPES = new LinkedHashMap<>(); + static { + FEATURE_TYPES.put(Module.class, "Module"); + FEATURE_TYPES.put(Behaviour.class, "Behaviour"); + FEATURE_TYPES.put(Task.class, "Task"); + } + private enum InstructionStatus { + NONE, UNSURE, YES + } + + private FeatureDefinition feature; + private String description; + private InstructionStatus instr; + + FeatureTypeButton(FeatureDefinition feature) { + super(""); + this.feature = feature; + this.instr = InstructionProvider.class.isAssignableFrom(feature.getClazz()) ? InstructionStatus.UNSURE : InstructionStatus.NONE; + + description = FEATURE_TYPES.entrySet().stream() + .filter(e -> e.getKey().isAssignableFrom(feature.getClazz())) + .map(Map.Entry::getValue) + .collect(Collectors.joining(",")); + + setText(description.isEmpty() ? "-" : description.substring(0, 1)); + + updateStatus(feature); + feature.addStatusListener(this::updateStatus); + } + + private void updateStatus(FeatureDefinition feature) { + if (instr == InstructionStatus.UNSURE && feature.getInstance() != null) { + InstructionProvider p = (InstructionProvider) feature.getInstance(); + instr = p.instructions() == null && p.instructionsComponent() == null ? InstructionStatus.NONE : InstructionStatus.YES; + } + setEnabled(instr == InstructionStatus.YES); + setToolTipText(description + + (instr == InstructionStatus.NONE ? "" : + " - " + (isEnabled() ? "Show instructions" : "Feature not loaded, info unavailable"))); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!isEnabled() || instr != InstructionStatus.YES) return; + ((InstructionProvider) feature.getInstance()).showInstructions(feature.getName()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/IssueList.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/IssueList.java new file mode 100644 index 00000000..3adbe054 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/IssueList.java @@ -0,0 +1,46 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.extensions.plugins.PluginIssue; +import com.github.manolo8.darkbot.gui.utils.SimpleMouseListener; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; + +class IssueList extends JPanel { + + IssueList(IssueHandler issues, boolean inline) { + super(new MigLayout((inline ? "ins 0, gapx 5px, " : "") + "wrap 1", "[right]", "[top]")); + + setOpaque(false); + setupUI(issues); + issues.addListener(this::setupUI); + } + + private void setupUI(IssueHandler issues) { + removeAll(); + issues.getIssues().stream().map(this::getError).forEachOrdered(this::add); + setVisible(!issues.getIssues().isEmpty()); + } + + private JLabel getError(PluginIssue pluginIssue) { + JLabel label = UIUtils.setRed(new JLabel(pluginIssue.getMessage()), pluginIssue.preventsLoading()); + label.setToolTipText(pluginIssue.getDescription()); + label.addMouseListener(new SimpleMouseListener() { + @Override + public void mousePressed(MouseEvent e) { + ToolTipManager ttm = ToolTipManager.sharedInstance(); + int oldDelay = ttm.getInitialDelay(); + ttm.setInitialDelay(0); + ttm.mouseMoved(new MouseEvent(label, 0, 0, 0, e.getX(), e.getY(), 0, false)); + SwingUtilities.invokeLater(() -> ttm.setInitialDelay(oldDelay)); + } + }); + label.setCursor(new Cursor(Cursor.HAND_CURSOR)); + return label; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginCard.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginCard.java new file mode 100644 index 00000000..aefd7031 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginCard.java @@ -0,0 +1,72 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Configurable; +import com.github.manolo8.darkbot.extensions.features.FeatureDefinition; +import com.github.manolo8.darkbot.extensions.features.FeatureRegistry; +import com.github.manolo8.darkbot.extensions.plugins.IssueHandler; +import com.github.manolo8.darkbot.extensions.plugins.Plugin; +import com.github.manolo8.darkbot.extensions.plugins.PluginIssue; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; + +public class PluginCard extends JPanel { + + private static final Border LOADED_BORDER = BorderFactory.createLineBorder(UIUtils.GREEN), + WARNING_BORDER = BorderFactory.createLineBorder(UIUtils.YELLOW), + ERROR_BORDER = BorderFactory.createLineBorder(UIUtils.RED); + private static int ALPHA = 32 << 24; + private static final Color LOADED_COLOR = new Color(UIUtils.GREEN.getRGB() + ALPHA, true), + WARNING_COLOR = new Color(UIUtils.YELLOW.getRGB() + ALPHA, true), + ERROR_COLOR = new Color(UIUtils.RED.getRGB() + ALPHA, true); + + PluginCard(Main main, Plugin plugin, FeatureRegistry featureRegistry) { + super(new MigLayout("fillx, gapy 0, ins 0 0 5px 0", "5px[]0px[]10px[]10px[grow]", "[]")); + setColor(plugin.getIssues()); + plugin.getIssues().addListener(this::setColor); + + add(new IssueList(plugin.getIssues(), false), "dock east"); + add(new PluginName(plugin.getDefinition()), "dock north"); + + featureRegistry.getFeatures(plugin).forEach(fd -> this.addFeature(main, fd)); + } + + private void addFeature(Main main, FeatureDefinition feature) { + add(new FeatureTypeButton(feature), "growx"); + if (Configurable.class.isAssignableFrom(feature.getClazz())) { + //noinspection unchecked + add(new FeatureConfigButton(main.config, (FeatureDefinition) feature)); + } else { + add(new JLabel()); + } + add(new FeatureCheckbox(feature)); + add(new IssueList(feature.getIssues(), true), "hidemode 2, wrap"); + } + + private void setColor(IssueHandler issues) { + if (issues.getLevel() == PluginIssue.Level.ERROR) { + setBorder(ERROR_BORDER); + setBackground(ERROR_COLOR); + } else if (issues.getLevel() == PluginIssue.Level.WARNING) { + setBorder(WARNING_BORDER); + setBackground(WARNING_COLOR); + } else { + setBorder(LOADED_BORDER); + setBackground(LOADED_COLOR); + } + setOpaque(false); + } + + @Override + public void paintComponent(Graphics g) { + // Panels don't render a background if set to opaque = false + // But opaque = false is required since the background is not completely opaque. + g.setColor(getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + super.paintComponent(g); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginDisplay.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginDisplay.java new file mode 100644 index 00000000..ec498d73 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginDisplay.java @@ -0,0 +1,68 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.extensions.plugins.PluginHandler; +import com.github.manolo8.darkbot.extensions.plugins.PluginListener; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.util.stream.Stream; + +public class PluginDisplay extends JPanel implements PluginListener { + + private Main main; + private MainButton pluginTab; + private PluginHandler pluginHandler; + + private JPanel pluginPanel; + + public PluginDisplay() { + super(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder()); + } + + public void setup(Main main, MainButton pluginTab) { + this.main = main; + this.pluginTab = pluginTab; + this.pluginHandler = main.pluginHandler; + add(setupUI()); + refreshUI(); + pluginHandler.addListener(this); + } + + private JComponent setupUI() { + pluginPanel = new JPanel(new MigLayout("wrap 1, fillx", "[fill]", "")); + JScrollPane scrollPane = new JScrollPane(pluginPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.getVerticalScrollBar().setUnitIncrement(25); + return scrollPane; + } + + private void refreshUI() { + Stream.concat( + pluginHandler.LOADING_EXCEPTIONS.stream().map(ExceptionCard::new), + Stream.concat( + pluginHandler.FAILED_PLUGINS.stream(), + pluginHandler.LOADED_PLUGINS.stream() + ).map(pl -> new PluginCard(main, pl, main.featureRegistry)) + ).forEach(pluginPanel::add); + pluginTab.setIcon(UIUtils.getIcon(pluginHandler.LOADING_EXCEPTIONS.isEmpty() && pluginHandler.FAILED_PLUGINS.isEmpty() ? "plugins" : "plugins_warn")); + validate(); + repaint(); + } + + @Override + public void beforeLoad() { + pluginPanel.removeAll(); + } + + @Override + public void afterLoadComplete() { + SwingUtilities.invokeLater(this::refreshUI); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginName.java b/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginName.java new file mode 100644 index 00000000..b239fa3d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/plugins/PluginName.java @@ -0,0 +1,32 @@ +package com.github.manolo8.darkbot.gui.plugins; + +import com.github.manolo8.darkbot.extensions.plugins.PluginDefinition; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; + +class PluginName extends JPanel { + + PluginName(PluginDefinition definition) { + super(new MigLayout("", "[]5px[]5px[]5px[]")); + JLabel name = new JLabel(definition.name); + Font baseFont = name.getFont(); + name.setFont(baseFont.deriveFont(baseFont.getStyle() | Font.BOLD)); + + JLabel version = new JLabel("v" + definition.version); + version.setFont(baseFont.deriveFont(baseFont.getStyle(), baseFont.getSize() * 0.8f)); + + JLabel by = new JLabel("by"); + + JLabel author = new JLabel(definition.author); + author.setFont(baseFont.deriveFont(baseFont.getStyle() | Font.ITALIC)); + + add(name); + add(version); + add(by); + add(author); + setOpaque(false); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/CloseButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/CloseButton.java new file mode 100644 index 00000000..0abd37c9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/CloseButton.java @@ -0,0 +1,20 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.MainGui; +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import java.awt.event.ActionEvent; + +public class CloseButton extends TitleBarButton { + + CloseButton(MainGui main) { + super(UIUtils.getIcon("close"), main); + super.actionColor = UIUtils.RED; + } + + @Override + public void actionPerformed(ActionEvent e) { + frame.tryClose(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigButton.java new file mode 100644 index 00000000..9deb2b06 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigButton.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.MainGui; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import java.awt.event.ActionEvent; + +public class ConfigButton extends TitleBarToggleButton { + + ConfigButton(MainGui frame) { + super(UIUtils.getIcon("config"), frame); + frame.addConfigVisibilityListener(this::setSelected); + setToolTipText(I18n.get("gui.config_button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + frame.toggleConfig(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigPicker.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigPicker.java new file mode 100644 index 00000000..e845a4ab --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigPicker.java @@ -0,0 +1,42 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigManager; + +import javax.swing.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ConfigPicker extends JComboBox { + + private Main main; + + public ConfigPicker() { + addActionListener(a -> { + if (main != null) main.configChange.send((String) getSelectedItem()); + }); + } + + public void setup(Main main) { + this.main = null; // Ensure no updates happen + removeAllItems(); + addItem(ConfigManager.DEFAULT); + try { + Files.list(Paths.get(ConfigManager.CONFIG_FOLDER)) + .map(Path::getFileName) + .map(Path::toString) + .filter(n -> !n.endsWith(ConfigManager.BACKUP + ConfigManager.EXTENSION)) + .filter(n -> n.endsWith(ConfigManager.EXTENSION)) + .map(n -> n.substring(0, n.length() - ConfigManager.EXTENSION.length())) + .forEach(this::addItem); + } catch (IOException e) { + e.printStackTrace(); + } + setSelectedItem(main.configManager.getConfigName()); + this.main = main; + setVisible(getModel().getSize() > 1); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigTitleBar.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigTitleBar.java new file mode 100644 index 00000000..2ba3f686 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ConfigTitleBar.java @@ -0,0 +1,31 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.utils.SimpleMouseListener; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ConfigTitleBar extends JPanel implements SimpleMouseListener { + + public ConfigTitleBar(JFrame frame, List tabs, ConfigPicker configs, MainButton plugins, Main main) { + super(new MigLayout("ins 0, gap 0, fill", + Stream.generate(() -> "[]").limit(tabs.size()).collect(Collectors.joining()) + + "[][grow, 30px::][][]", "[]")); + + for (JComponent tab : tabs) { + add(tab, "grow"); + } + add(configs, "grow, hidemode 2"); + add(new DragArea(frame), "grow"); + add(plugins, "grow"); + add(new PluginReloadButton(frame, main.pluginHandler), "grow"); + add(new HideButton(frame), "grow"); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/DragArea.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/DragArea.java new file mode 100644 index 00000000..a1437d4c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/DragArea.java @@ -0,0 +1,54 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.ConfigGui; +import com.github.manolo8.darkbot.gui.utils.SimpleMouseListener; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.gui.utils.window.WindowUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; + +public class DragArea extends JPanel implements SimpleMouseListener { + + private final JFrame frame; + // X and Y in relation to the DragArea component location, range 0 to 1 (percent) + private double internalX, internalY; + + DragArea(JFrame main) { + this.frame = main; + + setBorder(main instanceof ConfigGui ? UIUtils.getPartialBorder(1, 0, 1, 0) : UIUtils.getBorder()); + addMouseListener(this); + addMouseMotionListener(this); + } + + @Override + public void mousePressed(MouseEvent e) { + Point dragAreaLoc = getLocationOnScreen(); + Point clickLoc = e.getLocationOnScreen(); + internalX = (clickLoc.x - dragAreaLoc.x) / (double) getWidth(); + internalY = (clickLoc.y - dragAreaLoc.y) / (double) getHeight(); + } + + @Override + public void mouseDragged(MouseEvent e) { + WindowUtils.setMaximized(frame, false); + + Point clickLoc = e.getLocationOnScreen(); + Point dragAreaLoc = getLocation(); + frame.setLocation(clickLoc.x - (int)(internalX * getWidth()) - dragAreaLoc.x, clickLoc.y - (int) (internalY * getHeight()) - dragAreaLoc.y); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getLocationOnScreen().y == frame.getGraphicsConfiguration().getBounds().y) { + WindowUtils.setMaximized(frame, true); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) WindowUtils.toggleMaximized(frame); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java new file mode 100644 index 00000000..be633cab --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/ExtraButton.java @@ -0,0 +1,110 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.ExtraMenuProvider; +import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.gui.utils.PopupMenuListenerAdapter; +import com.github.manolo8.darkbot.gui.utils.JTitledPopupMenuSeparator; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.SystemUtils; +import com.github.manolo8.darkbot.utils.debug.SWFUtils; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +public class ExtraButton extends TitleBarToggleButton { + + private final Main main; + private final JPopupMenu extraOptions = new JPopupMenu("Extra Options"); + + private static final Set EXTRA_DECORATIONS = new LinkedHashSet<>(); + private static final Lazy clean = new Lazy.NoCache<>(); + + public static void setExtraDecorations(Stream provider) { + EXTRA_DECORATIONS.clear(); + provider.forEach(EXTRA_DECORATIONS::add); + clean.send(null); + } + + protected boolean empty = true; + + ExtraButton(Main main, JFrame frame) { + super(UIUtils.getIcon("hamburger"), frame); + + this.main = main; + + extraOptions.setBorder(UIUtils.getBorder()); + extraOptions.addPopupMenuListener(new PopupMenuListenerAdapter() { + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + setSelected(false); + } + }); + + clean.add(v -> { + extraOptions.removeAll(); + empty = true; + }); + + } + + private void rebuild(Main main) { + if (!empty) return; + for (ExtraMenuProvider extraDecoration : EXTRA_DECORATIONS) { + for (JComponent component : extraDecoration.getExtraMenuItems(main)) { + extraOptions.add(component); + } + } + empty = false; + } + + @Override + public void actionPerformed(ActionEvent e) { + rebuild(main); + if (isSelected()) extraOptions.show(this, 0, getHeight() - 1); + } + + @Feature(name = "Extra menu default provider", description = "Provides default extra buttons") + public static class DefaultExtraMenuProvider implements ExtraMenuProvider { + + @Override + public Collection getExtraMenuItems(Main main) { + List list = new ArrayList<>(); + + list.add(create("home", e -> { + String sid = main.statsManager.sid, instance = main.statsManager.instance; + if (sid == null || sid.isEmpty() || instance == null || instance.isEmpty()) return; + String url = instance + "?dosid=" + sid; + if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) SystemUtils.toClipboard(url); + else SystemUtils.openUrl(url); + })); + list.add(create("reload", e -> { + System.out.println("Triggering refresh: user requested"); + Main.API.handleRefresh(); + })); + list.add(create("discord", e -> SystemUtils.openUrl("https://discord.gg/KFd8vZT"))); + list.add(create("copy_sid", e -> SystemUtils.toClipboard(main.statsManager.sid))); + + + if (main.config.BOT_SETTINGS.DEV_STUFF) { + list.add(createSeparator("Dev stuff")); + list.add(create("Save SWF", e -> SWFUtils.dumpMainSWF())); + } + + return list; + } + + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/HideButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/HideButton.java new file mode 100644 index 00000000..7aae0f96 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/HideButton.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class HideButton extends TitleBarButton { + + HideButton(JFrame frame) { + super(UIUtils.getIcon("close"), frame); + super.actionColor = UIUtils.RED; + } + + @Override + public void actionPerformed(ActionEvent e) { + frame.setVisible(false); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MainTitleBar.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MainTitleBar.java new file mode 100644 index 00000000..4466e6b3 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MainTitleBar.java @@ -0,0 +1,30 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.gui.MainGui; +import com.github.manolo8.darkbot.gui.utils.SimpleMouseListener; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; + +public class MainTitleBar extends JPanel implements SimpleMouseListener { + + public MainTitleBar(Main main, MainGui frame) { + super(new MigLayout("ins 0, gap 0, fill", "[][][][][grow, 30px::][][][][][][]", "[]")); + + add(new ExtraButton(main, frame), "grow"); + add(new ConfigButton(frame), "grow"); + add(new StatsButton(frame), "grow, hidemode 2"); + add(new StartButton(main, frame), "grow"); + + add(new DragArea(frame), "grow"); + + add(new VisibilityButton(main, frame), "grow"); + add(new PinButton(frame), "grow"); + add(new TrayButton(frame), "grow, hidemode 2"); + add(new MinimizeButton(frame), "grow"); + add(new MaximizeButton(frame), "grow"); + add(new CloseButton(frame), "grow"); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MaximizeButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MaximizeButton.java new file mode 100644 index 00000000..3c04151a --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MaximizeButton.java @@ -0,0 +1,20 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.gui.utils.window.WindowUtils; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class MaximizeButton extends TitleBarButton { + + MaximizeButton(JFrame frame) { + super(UIUtils.getIcon("maximize"), frame); + } + + @Override + public void actionPerformed(ActionEvent e) { + WindowUtils.toggleMaximized(frame); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MinimizeButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MinimizeButton.java new file mode 100644 index 00000000..77fd3aeb --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/MinimizeButton.java @@ -0,0 +1,20 @@ +package com.github.manolo8.darkbot.gui.titlebar; + + +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; + +public class MinimizeButton extends TitleBarButton { + + MinimizeButton(JFrame frame) { + super(UIUtils.getIcon("minimize"), frame); + } + + @Override + public void actionPerformed(ActionEvent e) { + frame.setState(Frame.ICONIFIED); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/PinButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/PinButton.java new file mode 100644 index 00000000..1b1c7589 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/PinButton.java @@ -0,0 +1,29 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class PinButton extends TitleBarToggleButton { + + private static final Icon PIN = UIUtils.getIcon("pin"), UNPIN = UIUtils.getIcon("unpin"); + + PinButton(JFrame frame) { + super(PIN, frame); + setSelectedIcon(UNPIN); + setToolTipText(I18n.get("gui.pin_button")); + setSelected(ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.DISPLAY.ALWAYS_ON_TOP); + } + + @Override + public void actionPerformed(ActionEvent e) { + frame.setAlwaysOnTop(isSelected()); + + ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.DISPLAY.ALWAYS_ON_TOP = isSelected(); + ConfigEntity.changed(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/PluginReloadButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/PluginReloadButton.java new file mode 100644 index 00000000..c1f08853 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/PluginReloadButton.java @@ -0,0 +1,24 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.extensions.plugins.PluginHandler; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class PluginReloadButton extends TitleBarButton { + + private PluginHandler pluginHandler; + + PluginReloadButton(JFrame frame, PluginHandler pluginHandler) { + super(UIUtils.getIcon("reload"), frame); + setToolTipText(I18n.get("plugins.reload")); + this.pluginHandler = pluginHandler; + } + + @Override + public void actionPerformed(ActionEvent e) { + pluginHandler.updatePlugins(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/StartButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/StartButton.java new file mode 100644 index 00000000..2c8f1a7d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/StartButton.java @@ -0,0 +1,28 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class StartButton extends TitleBarToggleButton { + + private static final Icon PLAY = UIUtils.getIcon("play"), PAUSE = UIUtils.getIcon("pause"); + private final Main main; + + StartButton(Main main, JFrame frame) { + super(PLAY, frame); + setSelectedIcon(PAUSE); + setToolTipText(I18n.get("gui.start_button")); + this.main = main; + main.status.add(this::setSelected); + } + + @Override + public void actionPerformed(ActionEvent e) { + main.setRunning(isSelected()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/StatsButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/StatsButton.java new file mode 100644 index 00000000..3ec848d2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/StatsButton.java @@ -0,0 +1,20 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.MainGui; +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import java.awt.event.ActionEvent; + +public class StatsButton extends TitleBarButton { + + StatsButton(MainGui frame) { + super(UIUtils.getIcon("stats"), frame); + super.setVisible(false); + setToolTipText("Open stats view"); + } + + @Override + public void actionPerformed(ActionEvent e) { + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TitleBarButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TitleBarButton.java new file mode 100644 index 00000000..af7b3d1b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TitleBarButton.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.components.MainButton; + +import javax.swing.*; +import java.awt.event.ActionListener; + +abstract class TitleBarButton extends MainButton implements ActionListener { + + T frame; + + TitleBarButton(Icon icon, T frame) { + super(icon); + this.frame = frame; + } + + TitleBarButton(String text, T frame) { + super(text); + this.frame = frame; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TitleBarToggleButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TitleBarToggleButton.java new file mode 100644 index 00000000..3358ce97 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TitleBarToggleButton.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.gui.components.MainToggleButton; + +import javax.swing.*; +import java.awt.event.ActionListener; + +abstract class TitleBarToggleButton extends MainToggleButton implements ActionListener { + + T frame; + + TitleBarToggleButton(Icon icon, T frame) { + super(icon); + this.frame = frame; + } + + TitleBarToggleButton(String text, T frame) { + super(text); + this.frame = frame; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TrayButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TrayButton.java new file mode 100644 index 00000000..33427afd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/TrayButton.java @@ -0,0 +1,49 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.gui.MainGui; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; + +public class TrayButton extends TitleBarButton { + + private final TrayIcon icon; + private boolean shownMsg; + + TrayButton(JFrame frame) { + super(UIUtils.getIcon("tray"), frame); + setToolTipText(I18n.get("gui.tray_button")); + setVisible(SystemTray.isSupported()); + icon = new TrayIcon(MainGui.ICON, "DarkBot"); + icon.setImageAutoSize(true); + icon.addActionListener(l -> { + SystemTray.getSystemTray().remove(icon); + frame.setVisible(true); + }); + } + + @Override + public void actionPerformed(ActionEvent e) { + String username = HeroManager.instance.playerInfo.username; + if (!username.isEmpty()) + icon.setToolTip("DarkBot - " + username); + try { + SystemTray.getSystemTray().add(icon); + } catch (Exception ex) { + ex.printStackTrace(); + setVisible(false); // Disable minimizing to tray + return; + } + if (!shownMsg) { + icon.displayMessage(null, "Minimized to tray", TrayIcon.MessageType.NONE); + shownMsg = true; + } + frame.setVisible(false); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/titlebar/VisibilityButton.java b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/VisibilityButton.java new file mode 100644 index 00000000..ff45d2c1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/titlebar/VisibilityButton.java @@ -0,0 +1,54 @@ +package com.github.manolo8.darkbot.gui.titlebar; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.gui.components.ApiSettings; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import static com.github.manolo8.darkbot.Main.API; + +public class VisibilityButton extends TitleBarToggleButton { + + private static final Icon SHOW = UIUtils.getIcon("visibility"), HIDE = UIUtils.getIcon("visibility-off"); + + private final Main main; + + VisibilityButton(Main main, JFrame frame) { + super(SHOW, frame); + this.main = main; + + setSelectedIcon(HIDE); + setToolTipText(I18n.get("gui.visibility_button")); + setSelected(true); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (!SwingUtilities.isRightMouseButton(e)) return; + onRightClick(); + } + }); + } + + @Override + public void actionPerformed(ActionEvent e) { + toggleVisibility(main.config.BOT_SETTINGS.FULLY_HIDE_API, isSelected()); + } + + private void toggleVisibility(boolean minimizing, boolean visible) { + if (minimizing) API.setMinimized(!visible); + else API.setVisible(visible); + } + + private void onRightClick() { + Point p = getLocationOnScreen(); + p.translate(0, getHeight()); + new ApiSettings(main, p); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/trail/Line.java b/src/main/java/com/github/manolo8/darkbot/gui/trail/Line.java new file mode 100644 index 00000000..be05df91 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/trail/Line.java @@ -0,0 +1,102 @@ +package com.github.manolo8.darkbot.gui.trail; + +import com.github.manolo8.darkbot.core.utils.Location; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A method to smooth a hand-drawn line based on the McMaster + * line smoothing algorithm + * + * Heavily modified version, based on the implementation by: + * @author Derek Springer + * @link https://12inchpianist.com/2010/07/30/line-smoothing-in-java/ + */ +public class Line { + private final Location from, to; + private Location fromCopy, toCopy; + + public Line(Location from, Location to) { + this.from = from; + this.to = to; + } + + private Location getFrom() { + return fromCopy == null ? fromCopy = from.copy() : fromCopy.set(from.x, from.y); + } + + private Location getTo() { + return toCopy == null ? toCopy = to.copy() : toCopy.set(to.x, to.y); + } + + public static List> getSmoothedPaths(Collection lines) { + return getPaths(lines).stream().map(Line::smoothPath).collect(Collectors.toList()); + } + + /** + * Split a single list of lines in N paths + */ + private static List> getPaths(Collection lines) { + List> paths = new ArrayList<>(); + + List current = null; + Line last = null; + + for (Line line : lines) { + if (last == null || last.to != line.from) { + paths.add(current = new ArrayList<>()); + current.add(line.getFrom()); + } + last = line; + current.add(line.getTo()); + } + return paths; + } + + /** + * @param path A list of line segments representing a line + * @return A list line segments representing the smoothed line + */ + private static List smoothPath(List path) { + int size = path.size(); + if (size < 4) return path; + + Location[] points = new Location[5]; + for (int i = 0; i < 5; i++) points[i] = new Location(); + + for (int i = 0; i <= size; i++) { + if (i < size) { + Location loc = path.get(i); + points[i % points.length].set(loc.x, loc.y); + } else { + points[i % points.length] = null; + points[(i + 1) % points.length] = null; + } + if (i < 5) continue; + + smoothPoint(points, path.get(i - 2)); + } + return path; + } + + /** + * Find the new point for a smoothed line segment + * @param points The n points around the smoothed point + */ + private static void smoothPoint(Location[] points, Location oldPoint) { + int sumX = 0, sumY = 0, total = 0; + for(Location point : points) { + if (point == null) continue; + sumX += point.x; + sumY += point.y; + total++; + } + + oldPoint.x = ((sumX / total) + oldPoint.x) / 2; + oldPoint.y = ((sumY / total) + oldPoint.y) / 2; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorManager.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorManager.java new file mode 100644 index 00000000..9fbd3170 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorManager.java @@ -0,0 +1,70 @@ +package com.github.manolo8.darkbot.gui.tree; + +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.PlayerTag; +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.tree.ConfigNode; +import com.github.manolo8.darkbot.gui.tree.components.JBoolField; +import com.github.manolo8.darkbot.gui.tree.components.JCharField; +import com.github.manolo8.darkbot.gui.tree.components.JLabelField; +import com.github.manolo8.darkbot.gui.tree.components.JNumberField; +import com.github.manolo8.darkbot.gui.tree.components.JPlayerTagField; +import com.github.manolo8.darkbot.gui.tree.components.JRangeField; +import com.github.manolo8.darkbot.gui.tree.components.JShipConfigField; +import com.github.manolo8.darkbot.gui.tree.components.JStringField; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +public class EditorManager { + + // Standard editors for specific data types + private Map editorsByType = new HashMap<>(); + // Editors shared between diff TreeEditor instances + private Map, OptionEditor> sharedEditors = new HashMap<>(); + // Editors for specific @Editor annotations in fields + private Map, OptionEditor> editorsByClass = new HashMap<>(); + private OptionEditor defaultEditor = new JLabelField(); + + public EditorManager() { + this(null); + } + + public EditorManager(EditorManager shared) { + if (shared != null) this.sharedEditors = shared.sharedEditors; + defaultEditor.getComponent().setOpaque(false); + + addEditor(new JCharField(), Character.class); + addEditor(new JBoolField(), boolean.class); + addEditor(new JNumberField(), double.class, int.class); + addEditor(new JStringField(), String.class); + addEditor(new JShipConfigField(), Config.ShipConfig.class); + addEditor(new JRangeField(), Config.PercentRange.class); + addEditor(new JPlayerTagField(), PlayerTag.class); + } + + private void addEditor(OptionEditor editor, Class... types) { + for (Class type : types) this.editorsByType.put(type, editor); + editor.getComponent().setOpaque(false); + } + + public OptionEditor getEditor(ConfigField field) { + Class editorClass = field.getEditor(); + if (field.getEditor() == null) return editorsByType.getOrDefault(field.field.getType(), defaultEditor); + Map, OptionEditor> editorMap = field.isSharedEditor() ? sharedEditors : editorsByClass; + return editorMap.computeIfAbsent(editorClass, + c -> ReflectionUtils.createInstance(c, (Class) field.parent.getClass(), field.parent)); + } + + public int getWidthFor(ConfigNode node, FontMetrics font) { + if (I18n.getOrDefault(node.key, node.name).isEmpty()) return 0; + return node instanceof ConfigNode.Leaf ? + font.stringWidth(node.getLongestSibling()) + 10 : + font.stringWidth(I18n.getOrDefault(node.key, node.name)) + 5; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/OptionEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/OptionEditor.java new file mode 100644 index 00000000..cc5d7013 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/OptionEditor.java @@ -0,0 +1,12 @@ +package com.github.manolo8.darkbot.gui.tree; + +import com.github.manolo8.darkbot.config.tree.ConfigField; + +import javax.swing.*; + +public interface OptionEditor { + + JComponent getComponent(); + void edit(ConfigField field); + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeCell.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeCell.java new file mode 100644 index 00000000..cf33e41f --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeCell.java @@ -0,0 +1,96 @@ +package com.github.manolo8.darkbot.gui.tree; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.tree.ConfigNode; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; +import java.awt.*; + +public class TreeCell extends JPanel { + private final JComponent EMPTY_EDITOR = new JLabel(); + + private final EditorManager editors; + + private int nameWidth; + private int minHeight; + + private final JLabel name = new JLabel(); + private JComponent editor = EMPTY_EDITOR; + + public TreeCell(EditorManager editors) { + setLayout(new TreeCellLayout()); + + this.editors = editors; + setOpaque(false); + + add(name); + add(editor); + } + + public void setEditing(ConfigNode node) { + minHeight = getMinHeight(node); + nameWidth = editors.getWidthFor(node, name.getFontMetrics(name.getFont())); + + name.setText(I18n.getOrDefault(node.key, node.name)); + + if (node instanceof ConfigNode.Leaf) { + ConfigNode.Leaf leaf = (ConfigNode.Leaf) node; + setEditor(editors.getEditor(leaf.field), leaf.field); + } else { + setEditor(null, null); + } + + setToolTipText(I18n.getOrDefault(node.key + ".desc", node.description)); + } + + private void setEditor(OptionEditor newEditor, ConfigField field) { + if (editor != null) remove(editor); + if (newEditor != null) { + newEditor.edit(field); + add(editor = newEditor.getComponent()); + } else { + add(editor = EMPTY_EDITOR); + } + } + + /** + * Minimum height based on the depth. Lower depth nodes should be taller + * @param node The node to get height for + * @return the minimum height the component should have + */ + private int getMinHeight(ConfigNode node) { + return node instanceof ConfigNode.Leaf || node.getDepth() > 1 ? AdvancedConfig.ROW_HEIGHT : AdvancedConfig.HEADER_HEIGHT; + } + + private class TreeCellLayout implements LayoutManager { + @Override + public void addLayoutComponent(String name, Component comp) {} + @Override + public void removeLayoutComponent(Component comp) {} + + @Override + public Dimension preferredLayoutSize(Container parent) { + Dimension dim = parent.getComponent(1).getPreferredSize(); + dim.width += nameWidth; + dim.height = Math.max(dim.height, minHeight); + return dim; + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return preferredLayoutSize(parent); // Serves no real purpose + } + + @Override + public void layoutContainer(Container parent) { + Dimension editorSize = parent.getComponent(1).getPreferredSize(); + + int height = Math.max(minHeight, editorSize.height); + parent.getComponent(0).setBounds(0, 0, nameWidth, height); + parent.getComponent(1).setBounds(nameWidth, (height - editorSize.height) / 2, editorSize.width, editorSize.height); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeEditor.java new file mode 100644 index 00000000..1761f0b7 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeEditor.java @@ -0,0 +1,41 @@ +package com.github.manolo8.darkbot.gui.tree; + +import com.github.manolo8.darkbot.config.tree.ConfigNode; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeCellEditor; +import javax.swing.tree.DefaultTreeCellRenderer; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.EventObject; + +public class TreeEditor extends DefaultTreeCellEditor { + + private final TreeCell treeCell; + + public TreeEditor(JTree tree, EditorManager editors) { + super(tree, new DefaultTreeCellRenderer()); + + this.treeCell = new TreeCell(editors); + } + + public JTree getTree() { + return tree; + } + + @Override + public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, + boolean expanded, boolean leaf, int row) { + treeCell.setEditing((ConfigNode) value); + return treeCell; + } + + @Override + public boolean isCellEditable(EventObject e) { + if (e == null || e.getSource() != tree || !(e instanceof MouseEvent)) return false; + + lastPath = tree.getClosestPathForLocation(((MouseEvent)e).getX(), ((MouseEvent)e).getY()); + return lastPath != null && tree.getModel().isLeaf(lastPath.getLastPathComponent()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeRenderer.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeRenderer.java new file mode 100644 index 00000000..bc2ee239 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/TreeRenderer.java @@ -0,0 +1,23 @@ +package com.github.manolo8.darkbot.gui.tree; + +import com.github.manolo8.darkbot.config.tree.ConfigNode; + +import javax.swing.*; +import javax.swing.tree.TreeCellRenderer; +import java.awt.*; + +public class TreeRenderer implements TreeCellRenderer { + private final TreeCell treeCell; + + public TreeRenderer(EditorManager editors) { + this.treeCell = new TreeCell(editors); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + treeCell.setEditing((ConfigNode) value); + return treeCell; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/ExtraNpcInfoEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/ExtraNpcInfoEditor.java new file mode 100644 index 00000000..73526c07 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/ExtraNpcInfoEditor.java @@ -0,0 +1,94 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.NpcExtraFlag; +import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.gui.utils.JCheckBoxMenuItemNoClose; +import com.github.manolo8.darkbot.gui.utils.PopupMenuListenerAdapter; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import javax.swing.table.TableCellEditor; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ExtraNpcInfoEditor extends AbstractCellEditor implements TableCellEditor { + + private JNpcInfoTable.ExtraNpcInfoList curr; + private Collection infos; + private javax.swing.JLabel button = new javax.swing.JLabel(); + + private Map options = new HashMap<>(); + private JPopupMenu extraOptions = new JPopupMenu("Extra options"); + + private int tooltipDelay = -1; + + ExtraNpcInfoEditor() { + button.setOpaque(false); + button.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + + button.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (button.isShowing()) { + extraOptions.show(button, e.getX(), e.getY()); + if (tooltipDelay == -1) tooltipDelay = ToolTipManager.sharedInstance().getInitialDelay(); + ToolTipManager.sharedInstance().setInitialDelay(0); + } + } + }); + extraOptions.addPopupMenuListener(new PopupMenuListenerAdapter() { + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + stopCellEditing(); + if (tooltipDelay != -1) ToolTipManager.sharedInstance().setInitialDelay(tooltipDelay); + } + }); + } + + private void updateMenuEntries() { + if (NpcInfo.NPC_FLAGS.keySet().equals(options.keySet())) return; + extraOptions.removeAll(); + options = NpcInfo.NPC_FLAGS.values().stream() + .map(JMenuFlag::new) + .peek(extraOptions::add) + .collect(Collectors.toMap(fl -> fl.flag, Function.identity())); + } + + public Object getCellEditorValue() { + return curr; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + curr = (JNpcInfoTable.ExtraNpcInfoList) value; + infos = curr.infos; + button.setText(curr.toString()); + + updateMenuEntries(); + + NpcInfo info = infos.iterator().next(); + options.values().forEach(option -> option.setSelected(info.extra.has(option.flag))); + return button; + } + + private class JMenuFlag extends JCheckBoxMenuItemNoClose { + + private final String flag; + + JMenuFlag(NpcExtraFlag flag) { + super(flag.getName()); + this.flag = flag.getId(); + if (flag.getDescription() != null) setToolTipText(flag.getDescription()); + addActionListener(a -> { + infos.forEach(info -> info.extra.set(this.flag, isSelected())); + button.setText(curr.toString()); + }); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/InfoTable.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/InfoTable.java new file mode 100644 index 00000000..62276421 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/InfoTable.java @@ -0,0 +1,147 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.gui.components.MainButton; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.GenericTableModel; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.gui.utils.TableCharEditor; +import com.github.manolo8.darkbot.gui.utils.ToolTipHeader; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public abstract class InfoTable extends JTable implements OptionEditor { + private static final TableCharEditor CHAR_EDITOR = new TableCharEditor(); + + private JComponent component; + private Map data; + private Lazy listener; + private Supplier supplier; + + /** + * Creates a simple table from the model + * @param model The model to use + */ + public InfoTable(T model) { + this(model, null, null, null); + } + + /** + * Creates a simple table from the data + * @param clazz The type of elem + * @param data The data that this table uses. + */ + public InfoTable(Class clazz, Map data) { + this(clazz, data, null, null); + } + + /** + * Creates a generic info table with a model backed by the data + * @param clazz The type of element the table holds + * @param data The data that this table uses. + * @param listener Nullable, the listener to send modifications to. No Add/remove button if null. + * @param supplier Nullable, the supplier for new instances of elements. No add button if null. + */ + public InfoTable(Class clazz, Map data, Lazy listener, Supplier supplier) { + this((T) new GenericTableModel<>(clazz, data, listener), data, listener, supplier); + } + + /** + * Creates an info table with the model & data provided + * @param model The model to use + * @param data Nullable, the data that this table uses. No Add/remove button if null. + * @param listener Nullable, the listener to send modifications to. No Add/remove button if null. + * @param supplier Nullable, the supplier for new instances of elements. No add button if null. + */ + public InfoTable(T model, Map data, Lazy listener, Supplier supplier) { + super(model); + getColumnModel().getColumn(0).setPreferredWidth(200); + setDefaultEditor(Character.class, CHAR_EDITOR); + + setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + if (model instanceof GenericTableModel) { + setTableHeader(new ToolTipHeader(getColumnModel(), (GenericTableModel) model)); + } + getTableHeader().setReorderingAllowed(false); + + TableRowSorter sorter = new TableRowSorter<>((T) getModel()); + setRowSorter(sorter); + + component = new JPanel(new MigLayout("ins 0, gap 0, fill", "[grow][][][]", "[][grow]")); + + component.add(new JSearchField<>(sorter, extraFilters()), "grow, cell 0 0"); + component.add(new JScrollPane(this), "grow, span, cell 0 1"); + + if (data != null && listener != null) { + this.data = data; + this.listener = listener; + if (supplier != null) { + this.supplier = supplier; + getComponent().add(new AddButton(), "cell 2 0"); + getComponent().add(new RemoveButton(), "cell 3 0"); + } + } + + component.setPreferredSize(new Dimension(550, 270)); + } + + protected RowFilter extraFilters() { + return null; + } + + @Override + public JComponent getComponent() { + return component; + } + + @Override + public void edit(ConfigField ignore) {} + + private class AddButton extends MainButton { + AddButton() { + super(UIUtils.getIcon("add")); + super.actionColor = UIUtils.GREEN; + } + + @Override + public void actionPerformed(ActionEvent e) { + String name = JOptionPane.showInputDialog(InfoTable.this.getComponent(), "Name (must be unique):", "New element", JOptionPane.QUESTION_MESSAGE); + if (name == null) return; + if (data.containsKey(name)) { + Popups.showMessageAsync("Error", "Name already in use", JOptionPane.ERROR_MESSAGE); + return; + } + data.put(name, supplier.get()); + listener.send(name); + } + } + + private class RemoveButton extends MainButton { + RemoveButton() { + super(UIUtils.getIcon("remove")); + super.actionColor = UIUtils.RED; + } + + @Override + public void actionPerformed(ActionEvent e) { + List toRemove = Arrays.stream(InfoTable.super.getSelectedRows()) + .mapToObj(row -> (String) getValueAt(row, 0)) + .peek(data::remove) + .collect(Collectors.toList()); + toRemove.forEach(listener::send); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JActionTable.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JActionTable.java new file mode 100644 index 00000000..fd10fffb --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JActionTable.java @@ -0,0 +1,21 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.ActionInfo; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.GenericTableModel; + +import javax.swing.*; +import java.util.Arrays; + +public class JActionTable extends InfoTable implements OptionEditor { + + public JActionTable(Config.ExtraActions extra) { + super(ActionInfo.class, extra.ACTION_INFOS, extra.MODIFIED_ACTIONS, ActionInfo::new); + + getRowSorter().setSortKeys(Arrays.asList(new RowSorter.SortKey(1, SortOrder.DESCENDING), + new RowSorter.SortKey(0, SortOrder.DESCENDING))); + } + +} + diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JBoolField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JBoolField.java new file mode 100644 index 00000000..107d0702 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JBoolField.java @@ -0,0 +1,36 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import javax.swing.*; +import java.awt.*; + +public class JBoolField extends JCheckBox implements OptionEditor { + + private ConfigField field; + + public JBoolField() { + super.addChangeListener(e -> { + if (field != null) field.set(this.isSelected()); + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + setSelected(field.get()); + this.field = field; + } + + @Override + public Insets getInsets() { + return new Insets(0, -1, 0, 0); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JBoxInfoTable.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JBoxInfoTable.java new file mode 100644 index 00000000..254f686b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JBoxInfoTable.java @@ -0,0 +1,21 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.BoxInfo; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.GenericTableModel; + +import javax.swing.*; +import java.util.Arrays; + +public class JBoxInfoTable extends InfoTable implements OptionEditor { + + public JBoxInfoTable(Config.Collect collect) { + super(BoxInfo.class, collect.BOX_INFOS, collect.ADDED_BOX, BoxInfo::new); + + getRowSorter().setSortKeys(Arrays.asList(new RowSorter.SortKey(1, SortOrder.DESCENDING), + new RowSorter.SortKey(3, SortOrder.ASCENDING), + new RowSorter.SortKey(0, SortOrder.DESCENDING))); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JCharField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JCharField.java new file mode 100644 index 00000000..5ff266d5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JCharField.java @@ -0,0 +1,66 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.GeneralDocumentListener; + +import javax.swing.*; +import javax.swing.text.AbstractDocument; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DocumentFilter; +import java.awt.*; +import java.util.Objects; + +public class JCharField extends JTextField implements OptionEditor { + + private static final char EMPTY = (char) 0; + private static final DocumentFilter SINGLE_CHAR_DOCUMENT = new DocumentFilter(){ + @Override + public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { + super.replace(fb, 0, fb.getDocument().getLength(), text == null || text.isEmpty() || text.equals("\b") ? "" : text.substring(text.length() - 1), attrs); + } + }; + + private ConfigField field; + + public JCharField() { + super(1); + setMargin(new Insets(0, 3, 0, 3)); + putClientProperty("JComboBox.isTableCellEditor", true); // Make L&F further reduce margin/border + + ((AbstractDocument) getDocument()).setDocumentFilter(SINGLE_CHAR_DOCUMENT); + getDocument().addDocumentListener((GeneralDocumentListener) e -> setValue(getValue())); + setHorizontalAlignment(CENTER); + setCaretColor(new Color(0, 0, 0, 0)); + setHighlighter(null); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + setText(Objects.toString(field.get(), "")); + this.field = field; + } + + public Character getValue() { + if (field != null && field.isPrimitive() && getText().isEmpty()) return EMPTY; + return getText().isEmpty() ? null : getText().charAt(0); + } + + protected void setValue(Character value) { + if (field != null) field.set(value); + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JCheckboxListField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JCheckboxListField.java new file mode 100644 index 00000000..283adff1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JCheckboxListField.java @@ -0,0 +1,127 @@ +package com.github.manolo8.darkbot.gui.tree.components; + + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.types.Options; +import com.github.manolo8.darkbot.config.types.suppliers.OptionList; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import javax.accessibility.Accessible; +import javax.swing.*; +import javax.swing.plaf.basic.ComboPopup; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class JCheckboxListField extends JComboBox implements OptionEditor { + private final Map>, OptionList> optionInstances = new HashMap<>(); + private OptionList options; + private Collection elements; + protected ConfigField field; + + + private boolean keepOpen; + private transient ActionListener listener; + + public JCheckboxListField() { + putClientProperty("JComboBox.isTableCellEditor", true); + addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + showPopup(); + } + }); + } + + @Override + public void updateUI() { + setRenderer(null); + removeActionListener(listener); + super.updateUI(); + listener = e -> { + if ((e.getModifiers() & AWTEvent.MOUSE_EVENT_MASK) != 0) { + updateItem(getSelectedIndex()); + keepOpen = true; + } + }; + + javax.swing.JLabel label = new javax.swing.JLabel(" "); + JCheckBox check = new JCheckBox(" "); + check.setOpaque(true); + setRenderer((JList list, String text, int index, boolean isSelected, boolean cellHasFocus) -> { + if (index < 0) { + label.setText(elements.size() + "/" + options.getOptions().size() + " selected"); + return label; + } + check.setText(text); + check.setToolTipText(options.getTooltip(text)); + check.setSelected(elements.contains(options.getValue(text))); + check.setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); + check.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + return check; + }); + addActionListener(listener); + getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "checkbox-select"); + getActionMap().put("checkbox-select", new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { + Accessible a = getAccessibleContext().getAccessibleChild(0); + if (a instanceof ComboPopup) { + updateItem(((ComboPopup) a).getList().getSelectedIndex()); + } + } + }); + } + + private void updateItem(int index) { + if (!isPopupVisible()) return; + String option = getItemAt(index); + Object val = options.getValue(option); + if (elements.contains(val)) elements.remove(val); + else elements.add(val); + field.set(elements); // Make it update config + + setSelectedIndex(-1); + setSelectedItem(getItemAt(index)); + } + + @Override + public void setPopupVisible(boolean v) { + if (keepOpen) keepOpen = false; + else super.setPopupVisible(v); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + options = optionInstances.computeIfAbsent( + field.field.getAnnotation(Options.class).value(), ReflectionUtils::createInstance); + elements = field.get(); + + if (getModel() != options) setModel(options); + + + this.field = field; + } + + @Override + public Dimension getPreferredSize() { + Dimension d = super.getPreferredSize(); + d.width = Math.max(d.width, 180); + return AdvancedConfig.forcePreferredHeight(d); + } + +} + diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JFileOpener.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JFileOpener.java new file mode 100644 index 00000000..31337b7d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JFileOpener.java @@ -0,0 +1,60 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.Strings; + +import javax.swing.*; +import java.awt.*; +import java.io.File; + +public class JFileOpener extends JButton implements OptionEditor { + + private JFileChooser fc; + + private ConfigField field; + + public JFileOpener() { + putClientProperty("JButton.buttonType", "square"); + addActionListener(e -> { + if (fc == null) { + fc = new JFileChooser(new File(".")) { + @Override + protected JDialog createDialog(Component parent) throws HeadlessException { + JDialog dialog = super.createDialog(parent); + dialog.setAlwaysOnTop(true); + return dialog; + } + }; + } + if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + field.set(fc.getSelectedFile().getAbsolutePath()); + setText(Strings.fileName(field.get())); + } + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = field; + setText(Strings.fileName(field.get())); + } + + @Override + public void setText(String text) { + super.setText(text); + setPreferredSize(new Dimension(getFontMetrics(getFont()).stringWidth(getText()) + 16, 0)); + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JLabel.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JLabel.java new file mode 100644 index 00000000..2593410c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JLabel.java @@ -0,0 +1,7 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +@Deprecated +public class JLabel extends javax.swing.JLabel { + public JLabel() {this("");} + public JLabel(String text) {super(text);} +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JLabelField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JLabelField.java new file mode 100644 index 00000000..3e22f64c --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JLabelField.java @@ -0,0 +1,47 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import javax.swing.*; +import java.awt.*; +import java.util.Objects; + +public class JLabelField extends javax.swing.JLabel implements OptionEditor { + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + setText(Objects.toString(field.get(), "")); + } + + @Override + public Dimension getPreferredSize() { + Dimension d = super.getPreferredSize(); + d.height = AdvancedConfig.ROW_HEIGHT; + return d; + } + + /** + * No-op methods improve performance when using this as a cell renderer, and they are not needed anyways. + */ + public void validate() {} + public void invalidate() {} + public void revalidate() {} + public void repaint(long tm, int x, int y, int width, int height) {} + public void repaint(Rectangle r) {} + public void repaint() {} + public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {} + public void firePropertyChange(String propertyName, char oldValue, char newValue) {} + public void firePropertyChange(String propertyName, short oldValue, short newValue) {} + public void firePropertyChange(String propertyName, int oldValue, int newValue) {} + public void firePropertyChange(String propertyName, long oldValue, long newValue) {} + public void firePropertyChange(String propertyName, float oldValue, float newValue) {} + public void firePropertyChange(String propertyName, double oldValue, double newValue) {} + public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {} +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JListField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JListField.java new file mode 100644 index 00000000..9113a04d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JListField.java @@ -0,0 +1,71 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.types.Options; +import com.github.manolo8.darkbot.config.types.suppliers.OptionList; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.ToolTipListRenderer; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.util.HashMap; +import java.util.Map; + +public class JListField extends JComboBox implements OptionEditor { + + private Map>, OptionList> optionInstances = new HashMap<>(); + private OptionList options; + protected ConfigField field; + + public JListField() { + putClientProperty("JComboBox.isTableCellEditor", true); + addActionListener(event -> { + if (field != null) updateValue(); + }); + addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + showPopup(); + } + }); + setRenderer(new ToolTipListRenderer(this)); + } + + protected void updateValue() { + field.set(options.getValue((String) getSelectedItem())); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + options = optionInstances.computeIfAbsent( + field.field.getAnnotation(Options.class).value(), ReflectionUtils::createInstance); + + if (getModel() != options) setModel(options); + + Object option = options.getText(field.get()); + if (getSelectedItem() != option) setSelectedItem(option); + + this.field = field; + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + + public String getToolTipFor(String value) { + if (options == null) return null; + return options.getTooltip(value); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JNpcInfoTable.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JNpcInfoTable.java new file mode 100644 index 00000000..cbd87de4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JNpcInfoTable.java @@ -0,0 +1,175 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.core.manager.StarManager; +import com.github.manolo8.darkbot.core.objects.Map; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.GenericTableModel; +import com.github.manolo8.darkbot.gui.utils.TableDoubleEditor; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Set; +import java.util.stream.Collectors; + +public class JNpcInfoTable extends InfoTable implements OptionEditor { + + private int filteredMap = -1; + private JComboBox mapFilter; + + public JNpcInfoTable(Config.Loot config) { + super(new NpcTableModel(config), config.NPC_INFOS, config.MODIFIED_NPC, NpcInfo::new); + + super.getComponent().setPreferredSize(new Dimension(550, 270)); + + getRowSorter().setSortKeys(Arrays.asList(new RowSorter.SortKey(3, SortOrder.DESCENDING), + new RowSorter.SortKey(2, SortOrder.ASCENDING), + new RowSorter.SortKey(0, SortOrder.DESCENDING))); + + setDefaultEditor(ExtraNpcInfoList.class, new ExtraNpcInfoEditor()); + setDefaultEditor(Double.class, new TableDoubleEditor()); + + mapFilter = new JComboBox<>(); + + mapFilter.addActionListener(e -> { + if (mapFilter.getSelectedItem() == null) return; + filteredMap = ((Map) mapFilter.getSelectedItem()).id; + getRowSorter().allRowsChanged(); + }); + config.MODIFIED_NPC.add(s -> updateMapList(config.NPC_INFOS.values())); + updateMapList(config.NPC_INFOS.values()); + + mapFilter.setSelectedIndex(0); + getComponent().add(mapFilter, "grow, cell 1 0"); + } + + private void updateMapList(Collection npcInfos) { + Map map = (Map) mapFilter.getSelectedItem(); + mapFilter.removeAllItems(); + mapFilter.addItem(new Map(-1, "*", false, false)); + + Set maps = npcInfos.stream().flatMap(n -> n.mapList.stream()).collect(Collectors.toSet()); + StarManager.getAllMaps().stream().filter(m -> m.id >= 0 && maps.contains(m.id)).forEach(mapFilter::addItem); + + mapFilter.setSelectedItem(map); + } + + @Override + protected RowFilter extraFilters() { + return new NpcMapFilter(); + } + + @Override + public JComponent getComponent() { + ((NpcTableModel) getModel()).refreshTable(); + return super.getComponent(); + } + + protected static class NpcTableModel extends GenericTableModel { + + private Config.Loot config; + private boolean grouped; + private java.util.Map> NPC_INFOS = new HashMap<>(); + + NpcTableModel(Config.Loot config) { + super(NpcInfo.class, config.NPC_INFOS, config.MODIFIED_NPC); + this.config = config; + this.grouped = config.GROUP_NPCS; + updateTable(); + config.MODIFIED_NPC.add(n -> updateEntry(n, config.NPC_INFOS.get(n))); + } + + public void refreshTable() { + if (grouped != config.GROUP_NPCS) { + grouped = config.GROUP_NPCS; + updateTable(); + } + } + + public void updateTable() { + NPC_INFOS.clear(); + this.setNumRows(0); + config.NPC_INFOS.forEach(this::updateEntry); + } + + protected void updateEntry(String originalName, NpcInfo info) { + if (config == null) return; // Before setup, just ignore entries + String name = simplifyName(originalName); + + if (info == null) { // Remove info + if (!NPC_INFOS.containsKey(name)) return; + config.NPC_INFOS.entrySet().removeIf(e -> simplifyName(e.getKey()).equals(name)); + updateTable(); + return; + } + + Collection infoGroup; + if (name.equals(originalName)) infoGroup = Collections.singleton(info); + else (infoGroup = NPC_INFOS.getOrDefault(name, new ArrayList<>())).add(info); + + if (NPC_INFOS.containsKey(name)) { + info.copyOf(infoGroup.iterator().next()); + return; + } + NPC_INFOS.put(name, infoGroup); + addRow(new Object[]{name, info.radius, info.priority, info.kill, info.attackKey, info.attackFormation, new ExtraNpcInfoList(infoGroup)}); + } + + private String simplifyName(String name) { + if (!config.GROUP_NPCS || !name.matches("^[^\\d]+\\d{1,3}$")) return name; + + return name.replaceAll("\\d{1,3}$", " *"); + } + + @Override + public Class getColumnClass(int column) { + Class cl = super.getColumnClass(column); + if (cl == NpcInfo.ExtraNpcInfo.class) return ExtraNpcInfoList.class; + return cl; + } + + @Override + public void setValueAt(Object value, int row, int column) { + super.setValueAt(value, row, column); + NPC_INFOS.get((String) this.getValueAt(row, 0)).forEach(info -> { + if (column == 1) info.radius = (Double) value; + else if (column == 2) info.priority = (Integer) value; + else if (column == 3) info.kill = (Boolean) value; + else if (column == 4) info.attackKey = (Character) value; + else if (column == 5) info.attackFormation = (Character) value; + }); + ConfigEntity.changed(); + } + } + + public static class ExtraNpcInfoList { + Collection infos; + + ExtraNpcInfoList(Collection infos) { + this.infos = infos; + } + + @Override + public String toString() { + return infos.iterator().next().extra.toString(); + } + } + + private class NpcMapFilter extends RowFilter { + @Override + public boolean include(Entry entry) { + if (filteredMap == -1) return true; + NpcTableModel model = entry.getModel(); + return model.NPC_INFOS.get((String) model.getValueAt(entry.getIdentifier(), 0)) + .stream().anyMatch(n -> n.mapList.contains(filteredMap)); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JNumberField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JNumberField.java new file mode 100644 index 00000000..ee82ae28 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JNumberField.java @@ -0,0 +1,56 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.types.Num; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.SpinnerNumberMinMaxFix; + +import javax.swing.*; +import java.awt.*; + +public class JNumberField extends JSpinner implements OptionEditor { + + private ConfigField field; + + public JNumberField() { + addChangeListener(e -> { + if (field != null) field.set(getValue()); + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + Num number = field.field.getAnnotation(Num.class); + Number value = field.get(); + if (value == null) { + System.err.println("Null value in number config, using min as default: " + field.field); + value = number.min(); + } + + SpinnerNumberModel model; + try { + model = new SpinnerNumberMinMaxFix(value, number.min(), number.max(), number.step()); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + System.err.println("Failed to create editor for field, ignoring min & max: " + field.field); + + model = new SpinnerNumberMinMaxFix(value, null, null, number.step()); + } + setModel(model); + setPreferredSize(new Dimension(25 + (String.valueOf(number.max()).length() * 9), AdvancedConfig.EDITOR_HEIGHT)); + this.field = field; + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JPercentField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JPercentField.java new file mode 100644 index 00000000..9fcda9dd --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JPercentField.java @@ -0,0 +1,40 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import javax.swing.*; +import java.awt.*; + +public class JPercentField extends JSpinner implements OptionEditor { + + private ConfigField field; + + public JPercentField() { + super(new SpinnerNumberModel(0, 0, 1, 0.05)); + setEditor(new JSpinner.NumberEditor(this, "0%")); + + addChangeListener(e -> { + if (field != null) field.set(getValue()); + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + setValue(field.get()); + this.field = field; + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JPlayerTagField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JPlayerTagField.java new file mode 100644 index 00000000..ad8f0024 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JPlayerTagField.java @@ -0,0 +1,81 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.PlayerTag; +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.types.Tag; +import com.github.manolo8.darkbot.config.types.TagDefault; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.players.TagPopup; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import java.awt.*; +import java.util.stream.IntStream; + +public class JPlayerTagField extends JButton implements OptionEditor { + + private static final int ALPHA = 96; + private ConfigField field; + private TagPopup tagPopup; + private TagDefault unset; + + public JPlayerTagField() { + putClientProperty("JButton.buttonType", "square"); + setEditing(null); + + addActionListener(e -> tagPopup.show(this, 0, getHeight(), unset.toString())); + + tagPopup = new TagPopup(ConfigEntity.INSTANCE.getConfig().PLAYER_TAGS, tag -> { + if (field != null) { + field.set(tag); + setEditing(tag); + JPlayerTagField.this.revalidate(); + } + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = field; + Tag tag = field.field.getAnnotation(Tag.class); + if (tag == null) unset = TagDefault.UNSET; + else unset = tag.value(); + + setEditing(field.get()); + } + + private void setEditing(PlayerTag tag) { + if (tag == null) { + setText("(" + unset + ")"); + setBorder(null); + setBackground(UIUtils.BACKGROUND); + } else { + setText(tag.name); + setBorder(BorderFactory.createLineBorder(tag.color)); + setBackground(UIUtils.blendColor(tag.color, ALPHA)); + } + } + + @Override + public void setText(String text) { + super.setText(text); + setPreferredSize(new Dimension(getFontMetrics(getFont()).stringWidth(getText()) + 16, 0)); + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + + @Override + public boolean isDefaultButton() { + return false; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JRangeField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JRangeField.java new file mode 100644 index 00000000..6baec130 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JRangeField.java @@ -0,0 +1,68 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.SpinnerNumberMinMaxFix; + +import javax.swing.*; +import java.awt.*; + +public class JRangeField extends JPanel implements OptionEditor { + + private Config.PercentRange field; + private JSpinner min = createPercentSpinner(), max = createPercentSpinner(); + + public JRangeField() { + super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + + min.addChangeListener(e -> { + if (field != null) field.min = (double) min.getValue(); + if ((double) max.getValue() < (double) min.getValue()) { + max.setValue(min.getValue()); + } + }); + + max.addChangeListener(e -> { + if (field != null) field.max = (double) max.getValue(); + if ((double) max.getValue() < (double) min.getValue()) { + min.setValue(max.getValue()); + } + }); + add(min); + add(new javax.swing.JLabel(" - ")); + add(max); + } + + private JSpinner createPercentSpinner() { + JSpinner spinner = new JSpinner(new SpinnerNumberMinMaxFix(0, 0, 1, 0.05)) { + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + }; + spinner.setEditor(new JSpinner.NumberEditor(spinner, "0%")); + return spinner; + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + Config.PercentRange percent = field.get(); + min.setValue(percent.min); + max.setValue(percent.max); + this.field = percent; + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JSearchField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JSearchField.java new file mode 100644 index 00000000..fe52ffb0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JSearchField.java @@ -0,0 +1,58 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.gui.utils.GeneralDocumentListener; +import com.github.manolo8.darkbot.gui.utils.UIUtils; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableRowSorter; +import java.awt.*; +import java.util.Arrays; +import java.util.regex.PatternSyntaxException; + +public class JSearchField extends JTextField { + + private static final Icon SEARCH_ICON = UIUtils.getIcon("search"); + private static final Border MARGIN_BORDER = new EmptyBorder(0, SEARCH_ICON.getIconWidth() + 6, 0, 6); + + private final TableRowSorter sorter; + private final RowFilter extraFilter; + + public JSearchField(TableRowSorter sorter, RowFilter extraFilter) { + this.sorter = sorter; + this.extraFilter = extraFilter; + this.getDocument().addDocumentListener((GeneralDocumentListener) e -> update()); + update(); + } + + private void update() { + try { + sorter.setRowFilter(getFilterFor(RowFilter.regexFilter("(?i)" + getText(), 0))); + + setBackground(null); + } catch (PatternSyntaxException e) { + setBackground(UIUtils.RED); + } + } + + private RowFilter getFilterFor(RowFilter filter) { + return extraFilter == null ? filter : RowFilter.andFilter(Arrays.asList(extraFilter, filter)); + } + + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + int margin = (getHeight() - SEARCH_ICON.getIconHeight()) / 2; + SEARCH_ICON.paintIcon(this, graphics, margin, margin); + } + + @Override + public void setBorder(Border border) { + super.setBorder(new CompoundBorder(border, MARGIN_BORDER)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JShipConfigField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JShipConfigField.java new file mode 100644 index 00000000..a6eb7de1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JShipConfigField.java @@ -0,0 +1,81 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import javax.swing.*; +import java.awt.*; +import java.util.Objects; + +public class JShipConfigField extends JPanel implements OptionEditor { + + private JButton config1 = new ConfigButton(1), config2 = new ConfigButton(2); + private FormationField formation = new FormationField(); + + private Config.ShipConfig editing; + + public JShipConfigField() { + super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + setOpaque(false); + + add(config1); + add(config2); + add(new javax.swing.JLabel(" Formation ")); + add(formation); + } + + private void setConfig(int num) { + config1.setBackground(num == 1 ? UIManager.getColor("Tree.selectionBackground") : UIManager.getColor("Button.background")); + config2.setBackground(num == 2 ? UIManager.getColor("Tree.selectionBackground") : UIManager.getColor("Button.background")); + if (editing != null) { + this.editing.CONFIG = num; + ConfigEntity.changed(); + } + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.editing = null; + Config.ShipConfig conf = field.get(); + setConfig(conf.CONFIG); + formation.setText(Objects.toString(conf.FORMATION, "")); + formation.requestFocus(); + + this.editing = conf; + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + + private class ConfigButton extends JButton { + ConfigButton(int config) { + super(config + ""); + putClientProperty("JButton.buttonType", "square"); + //noinspection SuspiciousNameCombination + setPreferredSize(new Dimension(AdvancedConfig.EDITOR_HEIGHT, AdvancedConfig.EDITOR_HEIGHT)); + setFocusable(false); + + addActionListener(a -> setConfig(config)); + } + } + + private class FormationField extends JCharField { + @Override + protected void setValue(Character value) { + if (editing == null) return; + editing.FORMATION = value; + ConfigEntity.changed(); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JStringField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JStringField.java new file mode 100644 index 00000000..5d6ca5c7 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JStringField.java @@ -0,0 +1,46 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.config.types.Length; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; +import com.github.manolo8.darkbot.gui.utils.GeneralDocumentListener; + +import javax.swing.*; +import java.awt.*; +import java.util.Objects; + +public class JStringField extends JTextField implements OptionEditor { + + private ConfigField field; + + public JStringField() { + this.getDocument().addDocumentListener((GeneralDocumentListener) e -> { + if (field != null) field.set(getValue()); + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + setText(Objects.toString(field.get(), "")); + Length len = field.field.getAnnotation(Length.class); + setColumns(len == null ? 10 : len.value()); + this.field = field; + } + + public String getValue() { + return getText().isEmpty() ? null : getText(); + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JTimeField.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JTimeField.java new file mode 100644 index 00000000..7f830985 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/JTimeField.java @@ -0,0 +1,40 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.config.tree.ConfigField; +import com.github.manolo8.darkbot.gui.AdvancedConfig; +import com.github.manolo8.darkbot.gui.tree.OptionEditor; + +import javax.swing.*; +import java.awt.*; + +public class JTimeField extends JSpinner implements OptionEditor { + + private ConfigField field; + + public JTimeField() { + super(new SpinnerNumberModel(0, 0, 1, 0.05)); + setEditor(new DateEditor(this, "HH:mm:ss")); + + addChangeListener(e -> { + if (field != null) field.set(getValue()); + }); + } + + @Override + public JComponent getComponent() { + return this; + } + + @Override + public void edit(ConfigField field) { + this.field = null; + setValue(field.get()); + this.field = field; + } + + @Override + public Dimension getPreferredSize() { + return AdvancedConfig.forcePreferredHeight(super.getPreferredSize()); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/components/LangEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/LangEditor.java new file mode 100644 index 00000000..7a9ccde6 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/components/LangEditor.java @@ -0,0 +1,17 @@ +package com.github.manolo8.darkbot.gui.tree.components; + +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.utils.I18n; + +import javax.swing.*; + +public class LangEditor extends JListField { + @Override + protected void updateValue() { + super.updateValue(); + I18n.reloadProps(); + Popups.showMessageAsync( + I18n.get("language.changed.title"), + I18n.get("language.changed.content", I18n.getLocale().getDisplayName(I18n.getLocale()), I18n.get("translation.credit")), JOptionPane.INFORMATION_MESSAGE); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/GeneralDocumentListener.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/GeneralDocumentListener.java new file mode 100644 index 00000000..3729d4a0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/GeneralDocumentListener.java @@ -0,0 +1,25 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +@FunctionalInterface +public interface GeneralDocumentListener extends DocumentListener { + + @Override + default void insertUpdate(DocumentEvent e) { + update(e); + } + + @Override + default void removeUpdate(DocumentEvent e) { + update(e); + } + + @Override + default void changedUpdate(DocumentEvent e) { + update(e); + } + + void update(DocumentEvent e); +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/GenericTableModel.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/GenericTableModel.java new file mode 100644 index 00000000..654fff42 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/GenericTableModel.java @@ -0,0 +1,112 @@ +package com.github.manolo8.darkbot.gui.utils; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.types.Option; +import com.github.manolo8.darkbot.core.utils.Lazy; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.ReflectionUtils; + +import javax.swing.table.DefaultTableModel; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +public class GenericTableModel extends DefaultTableModel { + private final Field[] FIELDS; + private final String[] TOOLTIPS; + private final Class[] TYPES; + + private Map table = new HashMap<>(); + + public GenericTableModel(Class clazz, Map config, Lazy modified) { + super(getNames(clazz), 0); + + FIELDS = Arrays.stream(clazz.getDeclaredFields()) + .filter(f -> f.getAnnotation(Option.class) != null) + .toArray(Field[]::new); + + TOOLTIPS = prepend(clazz, Arrays.stream(FIELDS)) + .map(annotated -> annotated.getAnnotation(Option.class)) + .map(op -> I18n.getOrDefault(op.key() + ".desc", op.description())) + .map(s -> s.isEmpty() ? null : s) + .toArray(String[]::new); + + TYPES = prepend(String.class, Arrays.stream(FIELDS).map(Field::getType) + .map(ReflectionUtils::wrapped)).toArray(Class[]::new); + + config.forEach(this::updateEntry); + if (modified != null) modified.add(n -> updateEntry(n, config.get(n))); + } + + public String getToolTipAt(int column) { + return TOOLTIPS[column]; + } + + protected void updateEntry(String name, T data) { + if (data == null) { + if (table.containsKey(name)) removeEntry(name); + } else { + if (table.containsKey(name)) updateEntryRow(name, data); + else addEntry(name, data); + } + } + + private void addEntry(String name, T data) { + table.put(name, data); + addRow(prepend(name, Arrays.stream(FIELDS).map(f -> ReflectionUtils.get(f, data))).toArray(Object[]::new)); + } + + private void updateEntryRow(String name, T data) { + table.put(name, data); + for (int row = 0; row < getRowCount(); row++) { + if (!getValueAt(row, 0).equals(name)) continue; + for (int field = 0; field < FIELDS.length;) { + setValueAt(ReflectionUtils.get(FIELDS[field], data), row, ++field); + } + } + } + + private void removeEntry(String name) { + table.remove(name); + + for (int i = 0; i < getRowCount(); i++) { + if (getValueAt(i, 0).equals(name)) removeRow(i--); + } + } + + @Override + public boolean isCellEditable(int row, int column) { + return column > 0; + } + + @Override + public Class getColumnClass(int column) { + return TYPES[column]; + } + + @Override + public void setValueAt(Object value, int row, int column) { + super.setValueAt(value, row, column); + + if (!table.isEmpty()) { // Npc table ignore this table + ReflectionUtils.set(FIELDS[column - 1], table.get((String) this.getValueAt(row, 0)), value); + ConfigEntity.changed(); + } + } + + private static Stream prepend(T type, Stream stream) { + return Stream.concat(Stream.of(type), stream); + } + + private static String[] getNames(Class clazz) { + return prepend(clazz, Arrays.stream(clazz.getDeclaredFields())) + .map(annotated -> annotated.getAnnotation(Option.class)) + .filter(Objects::nonNull) + .map(op -> I18n.getOrDefault(op.key(), op.value())) + .toArray(String[]::new); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/JCheckBoxMenuItemNoClose.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/JCheckBoxMenuItemNoClose.java new file mode 100644 index 00000000..d4f60f00 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/JCheckBoxMenuItemNoClose.java @@ -0,0 +1,21 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.*; +import java.awt.event.MouseEvent; + +public class JCheckBoxMenuItemNoClose extends JCheckBoxMenuItem { + + public JCheckBoxMenuItemNoClose(String text) { + super(text); + } + + @Override + protected void processMouseEvent(MouseEvent evt) { + if (evt.getID() == MouseEvent.MOUSE_RELEASED && contains(evt.getPoint())) { + doClick(); + setArmed(true); + } else { + super.processMouseEvent(evt); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/JTitledPopupMenuSeparator.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/JTitledPopupMenuSeparator.java new file mode 100644 index 00000000..71b2e756 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/JTitledPopupMenuSeparator.java @@ -0,0 +1,62 @@ +package com.github.manolo8.darkbot.gui.utils; + +import com.formdev.flatlaf.util.UIScale; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.geom.Rectangle2D; + +public class JTitledPopupMenuSeparator extends JPopupMenu.Separator { + + private final JPopupMenu menu = new JPopupMenu(); + + public JTitledPopupMenuSeparator(String title) { + setBorder(new Border(title)); + setPreferredSize(new Dimension(0, menu.getFontMetrics(menu.getFont()).getHeight())); + } + + private class Border extends TitledBorder { + + public Border(String title) { + super(title); + } + + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + FontMetrics fm = c.getFontMetrics(menu.getFont()); + int titleWidth = fm.stringWidth(title); + int titleHeight = fm.getHeight(); + + // fill background + g.setColor(menu.getBackground()); + g.fillRect(x, y, width, titleHeight); + + int gap = UIScale.scale(4); + + Graphics2D g2 = (Graphics2D) g.create(); + try { + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(UIManager.getColor("Label.disabledForeground")); + + // paint separator lines + int sepWidth = (width - titleWidth) / 2 - gap - gap; + if (sepWidth > 0) { + int sy = y + Math.round(titleHeight / 2f); + float sepHeight = UIScale.scale((float) 1); + + g2.fill(new Rectangle2D.Float(x + gap, sy, sepWidth, sepHeight)); + g2.fill(new Rectangle2D.Float(x + width - gap - sepWidth, sy, sepWidth, sepHeight)); + } + + // draw title + int xt = x + ((width - titleWidth) / 2); + int yt = y + fm.getAscent(); + + g2.drawString(title, xt, yt); + } finally { + g2.dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/PopupMenuListenerAdapter.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/PopupMenuListenerAdapter.java new file mode 100644 index 00000000..268533cc --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/PopupMenuListenerAdapter.java @@ -0,0 +1,16 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + +public abstract class PopupMenuListenerAdapter implements PopupMenuListener { + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) {} + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} + + @Override + public void popupMenuCanceled(PopupMenuEvent e) {} +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/Popups.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/Popups.java new file mode 100644 index 00000000..73835b16 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/Popups.java @@ -0,0 +1,44 @@ +package com.github.manolo8.darkbot.gui.utils; + +import com.github.manolo8.darkbot.gui.MainGui; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; +import java.util.function.Consumer; + +public class Popups { + + public static void showMessageAsync(String title, String content, int type) { + showMessageAsync(title, (Object) content, type); + } + + public static void showMessageAsync(String title, Object content, int type) { + showMessageAsync(title, new JOptionPane(content, type)); + } + + public static void showMessageAsync(String title, JOptionPane pane) { + SwingUtilities.invokeLater(() -> showMessage(title, pane, null)); + } + + public static void showMessageSync(String title, JOptionPane pane) { + showMessageSync(title, pane, null); + } + + public static void showMessageSync(String title, JOptionPane pane, Consumer callback) { + try { + if (SwingUtilities.isEventDispatchThread()) showMessage(title, pane, callback); + else SwingUtilities.invokeAndWait(() -> showMessage(title, pane, callback)); + } catch (InterruptedException | InvocationTargetException e) { + e.printStackTrace(); + } + } + + private static void showMessage(String title, JOptionPane pane, Consumer callback) { + JDialog dialog = pane.createDialog(title); + if (callback != null) callback.accept(dialog); + dialog.setIconImage(MainGui.ICON); + dialog.setAlwaysOnTop(true); + dialog.setVisible(true); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/SearchField.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/SearchField.java new file mode 100644 index 00000000..630979d0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/SearchField.java @@ -0,0 +1,32 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.util.function.Consumer; + +public class SearchField extends JTextField { + + private static final Icon SEARCH_ICON = UIUtils.getIcon("search"); + private static final Border MARGIN_BORDER = new EmptyBorder(0, SEARCH_ICON.getIconWidth() + 6, 0, 6); + + public SearchField(Consumer filterChange) { + getDocument().addDocumentListener((GeneralDocumentListener) e -> filterChange.accept(getText())); + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + int margin = (getHeight() - SEARCH_ICON.getIconHeight()) / 2; + SEARCH_ICON.paintIcon(this, graphics, margin, margin); + } + + @Override + public void setBorder(Border border) { + super.setBorder(new CompoundBorder(border, MARGIN_BORDER)); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/SimpleMouseListener.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/SimpleMouseListener.java new file mode 100644 index 00000000..15a6a898 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/SimpleMouseListener.java @@ -0,0 +1,28 @@ +package com.github.manolo8.darkbot.gui.utils; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +public interface SimpleMouseListener extends MouseListener, MouseMotionListener { + @Override + default void mouseClicked(MouseEvent e){} + + @Override + default void mousePressed(MouseEvent e){} + + @Override + default void mouseReleased(MouseEvent e){} + + @Override + default void mouseEntered(MouseEvent e){} + + @Override + default void mouseExited(MouseEvent e){} + + @Override + default void mouseDragged(MouseEvent e){}; + + @Override + default void mouseMoved(MouseEvent e){}; +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/SimpleTreeListener.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/SimpleTreeListener.java new file mode 100644 index 00000000..1545c205 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/SimpleTreeListener.java @@ -0,0 +1,10 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; + +public interface SimpleTreeListener extends TreeModelListener { + default void treeNodesChanged(TreeModelEvent e) {} + default void treeNodesInserted(TreeModelEvent e){} + default void treeNodesRemoved(TreeModelEvent e){} +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/SpinnerNumberMinMaxFix.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/SpinnerNumberMinMaxFix.java new file mode 100644 index 00000000..bc2b9241 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/SpinnerNumberMinMaxFix.java @@ -0,0 +1,31 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.*; + +public class SpinnerNumberMinMaxFix extends SpinnerNumberModel { + + public SpinnerNumberMinMaxFix(Number value, Comparable minimum, Comparable maximum, Number stepSize) { + super(value, minimum, maximum, stepSize); + } + + public SpinnerNumberMinMaxFix(int value, int minimum, int maximum, int stepSize) { + super(value, minimum, maximum, stepSize); + } + + public SpinnerNumberMinMaxFix(double value, double minimum, double maximum, double stepSize) { + super(value, minimum, maximum, stepSize); + } + + @Override + public Object getNextValue() { + Object nextVal = super.getNextValue(); + return nextVal == null ? getMaximum() : nextVal; + } + + @Override + public Object getPreviousValue() { + Object prevVal = super.getPreviousValue(); + return prevVal == null ? getMinimum() : prevVal; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/Strings.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/Strings.java new file mode 100644 index 00000000..f604d48e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/Strings.java @@ -0,0 +1,17 @@ +package com.github.manolo8.darkbot.gui.utils; + +import java.io.File; + +public class Strings { + + public static String fileName(String path) { + if (path == null || path.isEmpty()) return "-"; + int split = path.lastIndexOf(File.separatorChar); + return split > 0 ? path.substring(split + 1) : path; + } + + public static String capitalize(String str) { + return Character.toUpperCase(str.charAt(0)) + str.substring(1); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/TableCharEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/TableCharEditor.java new file mode 100644 index 00000000..8c444a09 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/TableCharEditor.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.gui.utils; + +import com.github.manolo8.darkbot.gui.tree.components.JCharField; + +import javax.swing.*; + +public class TableCharEditor extends DefaultCellEditor { + public TableCharEditor() { + super(new JCharField()); + JCharField charField = (JCharField) getComponent(); + charField.removeActionListener(delegate); + charField.addActionListener(delegate = new EditorDelegate() { + public void setValue(Object value) { + charField.setText(value == null ? "" : value.toString()); + } + + public Object getCellEditorValue() { + return charField.getValue(); + } + }); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/TableDoubleEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/TableDoubleEditor.java new file mode 100644 index 00000000..63d30f6d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/TableDoubleEditor.java @@ -0,0 +1,54 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.*; +import javax.swing.border.LineBorder; +import java.awt.*; +import java.text.NumberFormat; + +public class TableDoubleEditor extends DefaultCellEditor { + + private NumberFormat nf = NumberFormat.getInstance(); + private Object value; + + public TableDoubleEditor() { + super(new JTextField()); + JTextField tf = (JTextField) getComponent(); + tf.setName("Table.editor"); + tf.setHorizontalAlignment(JTextField.RIGHT); + + tf.removeActionListener(delegate); + tf.addActionListener(delegate = new EditorDelegate() { + public void setValue(Object value) { + tf.setText(value == null ? "" : nf.format(value)); + tf.selectAll(); + } + + public Object getCellEditorValue() { + return tf.getText(); + } + }); + } + + public boolean stopCellEditing() { + try { + value = nf.parse((String) super.getCellEditorValue()).doubleValue(); + } catch (Exception e) { + ((JComponent)getComponent()).setBorder(new LineBorder(Color.red)); + return false; + } + return super.stopCellEditing(); + } + + public Component getTableCellEditorComponent(JTable table, Object value, + boolean isSelected, + int row, int column) { + this.value = null; + ((JComponent)getComponent()).setBorder(new LineBorder(Color.black)); + return super.getTableCellEditorComponent(table, value, isSelected, row, column); + } + + public Object getCellEditorValue() { + return value; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/ToolTipHeader.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/ToolTipHeader.java new file mode 100644 index 00000000..307c977d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/ToolTipHeader.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.gui.utils; + +import javax.swing.table.JTableHeader; +import javax.swing.table.TableColumnModel; +import java.awt.event.MouseEvent; + +public class ToolTipHeader extends JTableHeader { + private final GenericTableModel tableModel; + + public ToolTipHeader(TableColumnModel columnModel, GenericTableModel tableModel) { + super(columnModel); + this.tableModel = tableModel; + } + + public String getToolTipText(MouseEvent e) { + return tableModel.getToolTipAt(getTable().convertColumnIndexToModel(columnAtPoint(e.getPoint()))); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/ToolTipListRenderer.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/ToolTipListRenderer.java new file mode 100644 index 00000000..0be14cf9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/ToolTipListRenderer.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.gui.utils; + +import com.github.manolo8.darkbot.gui.tree.components.JListField; + +import javax.swing.*; +import java.awt.*; + +public class ToolTipListRenderer extends DefaultListCellRenderer { + + private final JListField field; + + public ToolTipListRenderer(JListField field) { + this.field = field; + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + if (isSelected && value instanceof String) setToolTipText(field.getToolTipFor((String) value)); + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/UIUtils.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/UIUtils.java new file mode 100644 index 00000000..0f21495b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/UIUtils.java @@ -0,0 +1,71 @@ +package com.github.manolo8.darkbot.gui.utils; + +import com.formdev.flatlaf.ui.FlatBorder; +import com.github.manolo8.darkbot.config.ConfigEntity; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.MatteBorder; +import java.awt.*; + +public class UIUtils { + + public static final Color GREEN = Color.decode("#3D6E3D"), + YELLOW = Color.decode("#6E6E28"), + RED = Color.decode("#6E2B28"), + BACKGROUND = UIManager.getColor("Viewport.background"), // Normal background of things + BORDER = UIManager.getColor("Component.borderColor"); // Normal border of things + //ACTION = new Color(50, 53, 55); // When hovering or clicking a btn + + public static ImageIcon getIcon(String name) { + return new ImageIcon(new ImageIcon(UIUtils.class.getResource("/" + name + ".png")).getImage() + .getScaledInstance(16, 16, Image.SCALE_SMOOTH)); + } + + public static Image getImage(String name) { + return new ImageIcon(UIUtils.class.getResource("/" + name + ".png")).getImage(); + } + + public static Insets getInsetConfig(boolean textPadding) { + int inset = ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.DISPLAY.BUTTON_SIZE; + return new Insets(inset, inset + (textPadding ? 4 : 0), inset, inset + (textPadding ? 4 : 0)); + } + + public static Border getBorderWithInsets(boolean textPadding) { + return new FlatBorder() { + @Override + public Insets getBorderInsets(Component c, Insets insets) { + return getInsetConfig(textPadding); + } + }; + } + + public static Border getBorder() { + return new FlatBorder(); + } + + public static Color blendColor(Color color, int alpha) { + float factor = (alpha != -1 ? alpha : color.getAlpha()) / 255f; + int red = (int) (BACKGROUND.getRed() * (1 - factor) + color.getRed() * factor); + int green = (int) (BACKGROUND.getGreen() * (1 - factor) + color.getGreen() * factor); + int blue = (int) (BACKGROUND.getBlue() * (1 - factor) + color.getBlue() * factor); + return new Color(red, green, blue); + } + + public static Border getPartialBorder(int top, int left, int bottom, int right) { + return new MatteBorder(top, left, bottom, right, BORDER); + } + + public static JLabel setRed(JLabel label, boolean set) { + Font baseFont = label.getFont(); + if (set) { + label.setForeground(UIUtils.RED.brighter().brighter()); + label.setFont(baseFont.deriveFont(baseFont.getStyle() | Font.BOLD)); + } else { + label.setForeground(null); + label.setFont(baseFont.deriveFont(baseFont.getStyle() & ~Font.BOLD)); + } + return label; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/window/ComponentBorderDragger.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/window/ComponentBorderDragger.java new file mode 100644 index 00000000..dae78ce0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/window/ComponentBorderDragger.java @@ -0,0 +1,143 @@ +package com.github.manolo8.darkbot.gui.utils.window; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; + +public class ComponentBorderDragger extends MouseAdapter { + + private JFrame frame; + + private byte direction; + protected static final byte NORTH = 1; + protected static final byte WEST = 2; + protected static final byte SOUTH = 4; + protected static final byte EAST = 8; + + private Cursor sourceCursor; + + private static Map cursors = new HashMap<>(); + + static { + cursors.put((byte) 1, Cursor.N_RESIZE_CURSOR); + cursors.put((byte) 2, Cursor.W_RESIZE_CURSOR); + cursors.put((byte) 4, Cursor.S_RESIZE_CURSOR); + cursors.put((byte) 8, Cursor.E_RESIZE_CURSOR); + cursors.put((byte) 3, Cursor.NW_RESIZE_CURSOR); + cursors.put((byte) 9, Cursor.NE_RESIZE_CURSOR); + cursors.put((byte) 6, Cursor.SW_RESIZE_CURSOR); + cursors.put((byte) 12, Cursor.SE_RESIZE_CURSOR); + } + + private Insets dragInsets; + private Dimension minSize; + + private Point basePoint; + + public ComponentBorderDragger(JFrame frame, Insets dragInsets, + Dimension minSize) { + super(); + this.frame = frame; + this.dragInsets = dragInsets; + this.minSize = minSize; + } + + @Override + public void mouseDragged(MouseEvent e) { + if (direction == 0) { + return; + } + + Point newPoint = e.getPoint(); + int x, y, width, height, newBasePointX, newBasePointY; + x = frame.getX(); + y = frame.getY(); + width = frame.getWidth(); + height = frame.getHeight(); + newBasePointX = newPoint.x; + newBasePointY = newPoint.y; + + if ((direction & EAST) == EAST) { + int newWidth; + newWidth = Math.max(minSize.width, width + newPoint.x + - basePoint.x); + width = newWidth; + } + if ((direction & SOUTH) == SOUTH) { + int novoAlto; + novoAlto = Math.max(minSize.height, height + newPoint.y + - basePoint.y); + height = novoAlto; + } + if ((direction & WEST) == WEST) { + int newWidth, newX; + newWidth = Math.max(minSize.width, width - newPoint.x + + basePoint.x); + newX = Math.min(x + width - minSize.width, x + newPoint.x + - basePoint.x); + + // Changing coordenates of new base point to refer to the new component position + newBasePointX -= newX - x; + x = newX; + width = newWidth; + } + if ((direction & NORTH) == NORTH) { + int newHeigth, newY; + newHeigth = Math.max(minSize.height, height - newPoint.y + + basePoint.y); + newY = Math.min(y + height - minSize.height, y + newPoint.y + - basePoint.y); + // Changing coordenates of new base point to refer to the new component position + newBasePointY -= newY - y; + y = newY; + height = newHeigth; + } + frame.setBounds(x, y, width, height); + basePoint = new Point(newBasePointX, newBasePointY); + } + + @Override + public void mouseMoved(MouseEvent e) { + Component originator = e.getComponent(); + if (direction == 0) { + sourceCursor = originator.getCursor(); + } + calculateDirection(e.getPoint(), e.getComponent().getSize()); + setCursor(e.getComponent()); + basePoint = e.getPoint(); + } + + @Override + public void mouseReleased(MouseEvent e) { + SwingUtilities.invokeLater(() -> { + frame.getContentPane().validate(); + frame.getContentPane().repaint(); + }); + } + + private void setCursor(Component component) { + if (direction == 0) { + component.setCursor(sourceCursor); + } else { + int cursorType = cursors.get(direction); + Cursor cursor = Cursor.getPredefinedCursor(cursorType); + component.setCursor(cursor); + } + } + + private void calculateDirection(Point point, Dimension componentSize) { + this.direction = calculateDirection(point, componentSize, 2); + } + + private byte calculateDirection(Point point, Dimension componentSize, double leniency) { + byte direction = 0; + if (point.x < dragInsets.left * leniency) direction |= WEST; + if (point.y < dragInsets.top * leniency) direction |= NORTH; + if (point.x > componentSize.width - dragInsets.right * leniency) direction |= EAST; + if (point.y > componentSize.height - dragInsets.bottom * leniency) direction |= SOUTH; + return direction; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/window/FrameResize.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/window/FrameResize.java new file mode 100644 index 00000000..8e74538d --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/window/FrameResize.java @@ -0,0 +1,25 @@ +package com.github.manolo8.darkbot.gui.utils.window; + +import javax.swing.*; +import java.awt.*; + +public class FrameResize extends JComponent { + + private JFrame parentFrame; + private Insets insets; + + public FrameResize(JFrame parentFrame, Insets insets) { + this.insets = insets; + this.parentFrame = parentFrame; + + ComponentBorderDragger dragger = new ComponentBorderDragger(parentFrame, insets, new Dimension(100, 100)); + addMouseListener(dragger); + addMouseMotionListener(dragger); + } + + @Override + public boolean contains(int x, int y) { + if (WindowUtils.isMaximized(parentFrame)) return false; + return x < insets.left || y < insets.top || getHeight() - y <= insets.bottom || getWidth() - x <= insets.right; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/window/WindowUtils.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/window/WindowUtils.java new file mode 100644 index 00000000..3f32f656 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/window/WindowUtils.java @@ -0,0 +1,69 @@ +package com.github.manolo8.darkbot.gui.utils.window; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; + +public class WindowUtils { + + public static void setupUndecorated(JFrame mainFrame, JPanel mainPanel) { + mainFrame.setUndecorated(true); + mainFrame.setLayout(null); + + FrameResize resizeFrame = new FrameResize(mainFrame, new Insets(5, 5, 5, 5)); + mainFrame.add(resizeFrame); + mainFrame.add(mainPanel); + + mainFrame.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + Dimension sizeIn = mainFrame.getSize(); + resizeFrame.setSize(sizeIn); + mainPanel.setSize(sizeIn); + } + + @Override + public void componentMoved(ComponentEvent e) { + setMaximizedInsets(mainFrame); + } + }); + Dimension sizeIn = mainFrame.getSize(); + resizeFrame.setSize(sizeIn); + mainPanel.setSize(sizeIn); + setMaximizedInsets(mainFrame); + + mainFrame.addWindowStateListener(e -> RepaintManager.currentManager(mainFrame).addInvalidComponent(mainFrame.getRootPane())); + } + + static boolean isMaximized(JFrame frame) { + return (frame.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0; + } + + public static void toggleMaximized(JFrame frame) { + setMaximized(frame, !isMaximized(frame)); + } + + public static void setMaximized(JFrame frame, boolean maximized) { + if (maximized == isMaximized(frame)) return; + setMaximizedInsets(frame); + if (maximized) frame.setExtendedState(frame.getExtendedState() | Frame.MAXIMIZED_BOTH); + else frame.setExtendedState(frame.getExtendedState() & ~Frame.MAXIMIZED_BOTH); + } + + /** + * Sets maximized bounds according to the display screen insets. + */ + private static void setMaximizedInsets(JFrame frame) { + GraphicsConfiguration gc = frame.getGraphicsConfiguration(); + Rectangle screenBounds = gc.getBounds(); + screenBounds.x = 0; + screenBounds.y = 0; + Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc); + frame.setMaximizedBounds(new Rectangle((screenBounds.x + screenInsets.left), + (screenBounds.y + screenInsets.top), + screenBounds.width - ((screenInsets.left + screenInsets.right)), + screenBounds.height - ((screenInsets.top + screenInsets.bottom)))); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/zones/ZoneEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/zones/ZoneEditor.java new file mode 100644 index 00000000..e0e07b29 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/zones/ZoneEditor.java @@ -0,0 +1,176 @@ +package com.github.manolo8.darkbot.gui.zones; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ZoneInfo; +import com.github.manolo8.darkbot.core.objects.Map; +import com.github.manolo8.darkbot.gui.MapDrawer; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.function.Consumer; + +public class ZoneEditor extends MapDrawer { + + private Color LINES = new Color(128, 128, 128, 128); + private Color HOVERING = new Color(0, 150, 200); + private Color SELECTING = new Color(0, 128, 255); + private Color ZONE = new Color(0, 255, 128, 64); + + private ZoneInfo zoneInfo = null; + private boolean selecting; + private Rect area = new Rect(); + + private int startX, startY; + + private Consumer mapChange = null; + + private class Rect { + int x1, x2, y1, y2; + + public void set(int x1, int y1, int x2, int y2) { + this.x1 = Math.min(x1, x2); + this.y1 = Math.min(y1, y2); + this.x2 = Math.max(x1, x2); + this.y2 = Math.max(y1, y2); + } + + public void update(double divisions) { + double widthDivision = width / divisions, heightDivision = height / divisions; + x1 -= x1 % widthDivision; + y1 -= y1 % heightDivision; + x2 += widthDivision - (x2 % widthDivision); + y2 += heightDivision - (y2 % heightDivision); + + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 > width) x2 = width; + if (y2 > height) y2 = height; + } + + private void draw(Graphics2D g2) { + g2.drawRect(x1, y1, x2 - x1 - (x2 == width ? 1 : 0), y2 - y1 - (y2 == height ? 1 : 0)); + } + } + + ZoneEditor() { + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + area.set(startX = e.getX(), startY = e.getY(), e.getX(), e.getY()); + selecting = true; + ZoneEditor.super.repaint(); + } + + @Override + public void mouseReleased(MouseEvent e) { + selecting = false; + area.set(startX, startY, e.getX(), e.getY()); + toggleSelection(SwingUtilities.isMiddleMouseButton(e), SwingUtilities.isLeftMouseButton(e)); + area.set(e.getX(), e.getY(), e.getX(), e.getY()); + ZoneEditor.super.repaint(); + } + }); + addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + area.set(startX, startY, e.getX(), e.getY()); + ZoneEditor.super.repaint(); + } + + @Override + public void mouseMoved(MouseEvent e) { + area.set(e.getX(), e.getY(), e.getX(), e.getY()); + ZoneEditor.super.repaint(); + } + }); + } + + void setup(Main main, java.util.Map zonesByMap) { + super.setup(main); + if (mapChange != null) main.mapManager.mapChange.remove(mapChange); + main.mapManager.mapChange.add(mapChange = map -> { + zoneInfo = zonesByMap.computeIfAbsent(map.id, id -> new ZoneInfo(config.BOT_SETTINGS.ZONE_RESOLUTION)); + ZoneEditor.this.repaint(); + }); + zoneInfo = zonesByMap.get(main.hero.map.id); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = setupDraw(g); + + synchronized (Main.UPDATE_LOCKER) { + drawZones(g2); + drawStaticEntities(g2); + drawMap(g2); + } + + if (zoneInfo == null) return; + + int res = config.BOT_SETTINGS.ZONE_RESOLUTION; + if (zoneInfo.resolution != res) zoneInfo.setResolution(res); + drawCustomZones(g2); + drawGrid(g2, res); + + if (!selecting && !hovering) return; + + g2.setColor(selecting ? SELECTING : HOVERING); + area.update(res); + area.draw(g2); + } + + @Override + protected void drawMap(Graphics2D g2) { + g2.setColor(TEXT_DARK); + g2.setFont(FONT_BIG); + drawString(g2, hero.map.name, mid, (height / 2) + 12, Align.MID); + } + + @Override + protected void drawCustomZones(Graphics2D g2) { + g2.setColor(ZONE); + drawCustomZone(g2, zoneInfo); + } + + private void drawGrid(Graphics2D g2, int resolution) { + g2.setColor(LINES); + for (int i = 1; i < resolution; i++) { + int x = i * width / resolution; + g2.drawLine(x, 0, x, height); + } + for (int i = 1; i < resolution; i++) { + int y = i * height / resolution; + g2.drawLine(0, y, width, y); + } + } + + private void toggleSelection(boolean toggle, boolean def) { + if (zoneInfo == null) return; + area.update(zoneInfo.resolution); + + int startX = mapToGridX(area.x1), startY = mapToGridY(area.y1), + endX = mapToGridX(area.x2), endY = mapToGridY(area.y2); + + if (toggle) { + zoneInfo.toggle(startX, startY, endX, endY); + config.changed = true; + return; + } + + boolean differ = false; + boolean sel = zoneInfo.get(startX, startY); + for (int x = startX; x < endX; x++) { + for (int y = startY; y < endY; y++) { + if (zoneInfo.get(x, y) != sel && (differ = true)) break; + } + if (differ) break; + } + + zoneInfo.set(startX, startY, endX, endY, !differ ? !sel : def); + config.changed = true; + } + + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/zones/ZonesEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/zones/ZonesEditor.java new file mode 100644 index 00000000..240b03e2 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/zones/ZonesEditor.java @@ -0,0 +1,41 @@ +package com.github.manolo8.darkbot.gui.zones; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.gui.components.TabbedPane; +import com.github.manolo8.darkbot.gui.zones.safety.SafetiesEditor; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; + +public class ZonesEditor extends JPanel { + + private TabbedPane tabbedPane = new TabbedPane(); + private ZoneEditor preferredZones = new ZoneEditor(); + private ZoneEditor avoidedZones = new ZoneEditor(); + private SafetiesEditor safeEditor = new SafetiesEditor(); + + public ZonesEditor() { + super(new MigLayout("ins 0, gap 0, wrap 3", "[grow][grow][grow]", "[][grow]")); + tabbedPane.setBorder(null); + + tabbedPane.addTab(null, "tabs.preferred_zones", preferredZones); + tabbedPane.addTab(null, "tabs.avoided_zones", avoidedZones); + tabbedPane.addTab(null, "tabs.safety_places", safeEditor); + + tabbedPane.getHeader().forEach(tab -> this.add(tab, "grow")); + add(tabbedPane, "span 3, grow"); + } + + public void setup(Main main) { + // Create zones for current map if missing + ConfigEntity.INSTANCE.getOrCreatePreferred(); + ConfigEntity.INSTANCE.getOrCreateAvoided(); + ConfigEntity.INSTANCE.getOrCreateSafeties(); + + preferredZones.setup(main, main.config.PREFERRED); + avoidedZones.setup(main, main.config.AVOIDED); + safeEditor.setup(main); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesDisplay.java b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesDisplay.java new file mode 100644 index 00000000..fb0abe32 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesDisplay.java @@ -0,0 +1,83 @@ +package com.github.manolo8.darkbot.gui.zones.safety; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.SafetyInfo; +import com.github.manolo8.darkbot.gui.MapDrawer; + +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Comparator; + +class SafetiesDisplay extends MapDrawer { + + private Color ZONE_HIGHLIGHT = new Color(0, 255, 128, 96); + private Color ZONE_SELECTED = new Color(0, 255, 128, 128); + private Color ZONE_SOLID = new Color(0, 255, 128); + + private SafetiesEditor editor; + private SafetyInfo closest; + + SafetiesDisplay(SafetiesEditor editor) { + this.editor = editor; + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + updateClosest(e); + if (closest != null) editor.edit(closest); + } + }); + addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + updateClosest(e); + repaint(); + } + }); + } + + private void updateClosest(MouseEvent e) { + double x = undoTranslateX(e.getX()), y = undoTranslateY(e.getY()); + closest = editor.safetyInfos.stream() + .filter(s -> s.entity != null && !s.entity.removed) + .min(Comparator.comparingDouble(s -> Math.pow(s.x - x, 2) + Math.pow(s.y - y, 2)))// squared distance + .orElse(null); + repaint(); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = setupDraw(g); + synchronized (Main.UPDATE_LOCKER) { + drawZones(g2); + drawStaticEntities(g2); + drawMap(g2); + if (editor.safetyInfos == null) return; + drawCustomZones(g2); + } + } + + @Override + protected void drawMap(Graphics2D g2) { + g2.setColor(TEXT_DARK); + g2.setFont(FONT_BIG); + drawString(g2, hero.map.name, mid, (height / 2) + 12, Align.MID); + } + + @Override + protected void drawCustomZones(Graphics2D g2) { + if (hovering && closest != null && closest != editor.editing) { + g2.setColor(ZONE_HIGHLIGHT); + drawSafeZone(g2, closest); + g2.setColor(ZONE_SOLID); + int radius = closest.radius(); + g2.drawOval(translateX(closest.x - radius), translateY(closest.y - radius), + translateX(closest.diameter()), translateY(closest.diameter())); + } + if (editor.editing != null) { + g2.setColor(ZONE_SELECTED); + drawSafeZone(g2, editor.editing); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesEditor.java new file mode 100644 index 00000000..1fa66a70 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesEditor.java @@ -0,0 +1,69 @@ +package com.github.manolo8.darkbot.gui.zones.safety; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.SafetyInfo; +import com.github.manolo8.darkbot.core.objects.Map; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.util.Set; +import java.util.function.Consumer; + +public class SafetiesEditor extends JPanel { + + Set safetyInfos = null; + SafetyInfo editing = null; + + private SafetiesList safetyList; + private SafetiesDisplay safetyDisplay; + private SafetyEditor safetyEditor; + + private Consumer onMapChange = m -> updateMap(); + + public SafetiesEditor() { + super(new MigLayout("ins 0, gap 0, wrap 2, fill", "[100px][grow]", "[grow][]")); + initComponents(); + setComponentPosition(); + } + + public void setup(Main main) { + main.config.ADDED_SAFETY.add(s -> { + safetyList.addOrUpdate(s); + safetyDisplay.repaint(); + }); + main.mapManager.mapChange.add(onMapChange); + updateMap(); + safetyDisplay.setup(main); + } + + private void initComponents() { + safetyList = new SafetiesList(this); + safetyDisplay = new SafetiesDisplay(this); + safetyEditor = new SafetyEditor(this); + } + + private void setComponentPosition() { + JScrollPane safetyListWrapper = new JScrollPane(safetyList); + safetyListWrapper.setBorder(BorderFactory.createEmptyBorder()); + add(safetyListWrapper, "grow"); + add(safetyDisplay, "grow"); + add(safetyEditor, "grow, span"); + } + + private void updateMap() { + safetyInfos = ConfigEntity.INSTANCE.getOrCreateSafeties(); + safetyList.refresh(); + safetyDisplay.repaint(); + } + + protected void edit(SafetyInfo editing) { + this.editing = null; + safetyEditor.edit(editing); + this.editing = editing; + + safetyList.setSelected(editing); + safetyDisplay.repaint(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesList.java b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesList.java new file mode 100644 index 00000000..18d666d8 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetiesList.java @@ -0,0 +1,90 @@ +package com.github.manolo8.darkbot.gui.zones.safety; + +import com.github.manolo8.darkbot.config.SafetyInfo; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; + +class SafetiesList extends JTable { + + private SafetiesEditor editor; + private SafetyTableModel model = new SafetyTableModel(); + + SafetiesList(SafetiesEditor editor) { + this.editor = editor; + setModel(model); + setTableHeader(null); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setAutoCreateRowSorter(true); + setFocusable(false); + setRowHeight(20); + setDefaultRenderer(SafetyInfo.class, new SafetyCellRenderer()); + + getSelectionModel().addListSelectionListener(lse -> { + if (!lse.getValueIsAdjusting()) + editor.edit(getSelectedRow() == -1 ? null : (SafetyInfo) getValueAt(getSelectedRow(), 0)); + }); + } + + void addOrUpdate(SafetyInfo safety) { + model.addOrUpdate(safety); + } + + void refresh() { + model.setRowCount(0); + for (SafetyInfo safetyInfo : editor.safetyInfos) addOrUpdate(safetyInfo); + } + + void setSelected(SafetyInfo safety) { + if (getSelectedRow() != -1 && getValueAt(getSelectedRow(), 0) == safety) return; + for (int i = 0; i < getRowCount(); i++) { + if (getValueAt(i, 0) != safety) continue; + setRowSelectionInterval(i, i); + return; + } + } + + private class SafetyTableModel extends DefaultTableModel { + SafetyTableModel() { + super(0, 1); + } + + void addOrUpdate(SafetyInfo safety) { + boolean remove = safety.entity == null || safety.entity.removed; + for (int i = 0; i < getRowCount(); i++) { + if (getValueAt(i, 0) != safety) continue; + if (remove) removeRow(i); + return; + } + if (!remove) addRow(new Object[]{safety}); + } + + @Override + public Class getColumnClass(int columnIndex) { + return SafetyInfo.class; + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + } + + private static class SafetyCellRenderer extends DefaultTableCellRenderer { + + Border padding = BorderFactory.createEmptyBorder(0, 5, 0, 5); + @Override + public JComponent getTableCellRendererComponent(JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBorder(BorderFactory.createCompoundBorder(getBorder(), padding)); + return this; + } + } + +} + + + diff --git a/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetyEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetyEditor.java new file mode 100644 index 00000000..63d13092 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/zones/safety/SafetyEditor.java @@ -0,0 +1,66 @@ +package com.github.manolo8.darkbot.gui.zones.safety; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.config.SafetyInfo; +import com.github.manolo8.darkbot.utils.I18n; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; + +class SafetyEditor extends JPanel { + + private JComboBox runEditor; + private JComboBox jumpEditor; + private JComboBox cbsEditor; + + SafetyEditor(SafetiesEditor editor) { + super(new MigLayout("wrap 3, fill", "[][][]")); + + runEditor = new JComboBox<>(SafetyInfo.RunMode.values()); + jumpEditor = new JComboBox<>(SafetyInfo.JumpMode.values()); + cbsEditor = new JComboBox<>(SafetyInfo.CbsMode.values()); + edit(null); + + add(new JLabel(I18n.get("safety_places.run_mode"))); + add(new JLabel(I18n.get("safety_places.jump_mode"))); + add(new JLabel(I18n.get("safety_places.cbs_mode"))); + add(runEditor, "grow"); + add(jumpEditor, "grow"); + add(cbsEditor, "grow"); + + runEditor.addActionListener(a -> { + if (editor.editing != null) { + editor.editing.runMode = runEditor.getItemAt(runEditor.getSelectedIndex()); + ConfigEntity.changed(); + } + }); + jumpEditor.addActionListener(a -> { + if (editor.editing != null) { + editor.editing.jumpMode = jumpEditor.getItemAt(jumpEditor.getSelectedIndex()); + ConfigEntity.changed(); + } + }); + cbsEditor.addActionListener(a -> { + if (editor.editing != null) { + editor.editing.cbsMode = cbsEditor.getItemAt(cbsEditor.getSelectedIndex()); + ConfigEntity.changed(); + } + }); + } + + void edit(SafetyInfo editing) { + runEditor.setEnabled(editing != null); + jumpEditor.setEnabled(editing != null && editing.type == SafetyInfo.Type.PORTAL); + cbsEditor.setEnabled(editing != null && editing.type == SafetyInfo.Type.CBS); + + setEdit(runEditor, editing == null ? null : editing.runMode); + setEdit(jumpEditor, editing == null ? null : editing.jumpMode); + setEdit(cbsEditor, editing == null ? null : editing.cbsMode); + } + + private void setEdit(JComboBox combo, T obj) { + if (obj == null) combo.setSelectedIndex(-1); + else combo.setSelectedItem(obj); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/CollectorModule.java b/src/main/java/com/github/manolo8/darkbot/modules/CollectorModule.java index 321741fd..ca5e4948 100644 --- a/src/main/java/com/github/manolo8/darkbot/modules/CollectorModule.java +++ b/src/main/java/com/github/manolo8/darkbot/modules/CollectorModule.java @@ -9,13 +9,17 @@ import com.github.manolo8.darkbot.core.objects.LocationInfo; import com.github.manolo8.darkbot.core.utils.Drive; import com.github.manolo8.darkbot.core.utils.Location; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.modules.utils.SafetyFinder; +import java.util.Comparator; import java.util.List; import static com.github.manolo8.darkbot.Main.API; import static java.lang.Math.cos; import static java.lang.StrictMath.sin; +@Feature(name = "Collector", description = "Resource-only collector module. Can cloack.") public class CollectorModule implements Module { private Main main; @@ -29,19 +33,19 @@ public class CollectorModule implements Module { private long invisibleTime; - Box current; + public Box current; private long waiting; - private int DISTANCE_FROM_DANGEROUS; + protected SafetyFinder safety; + protected long refreshing; - public CollectorModule() { - DISTANCE_FROM_DANGEROUS = 1500; - } + private int DISTANCE_FROM_DANGEROUS = 1500; @Override public void install(Main main) { this.main = main; + this.safety = new SafetyFinder(main); this.hero = main.hero; this.drive = main.hero.drive; @@ -51,6 +55,19 @@ public void install(Main main) { this.ships = main.mapManager.entities.ships; } + @Override + public void uninstall() { + safety.uninstall(); + } + + @Override + public String status() { + if (current == null) return "Roaming"; + + return current.isCollected() ? "Collecting " + current.type + " " + (waiting - System.currentTimeMillis()) + "ms" + : "Moving to " + current.type; + } + @Override public boolean canRefresh() { return isNotWaiting(); @@ -59,8 +76,8 @@ public boolean canRefresh() { @Override public void tick() { - if (isNotWaiting() && checkCurrentMap()) { - + if (isNotWaiting() && checkDangerousAndCurrentMap()) { + main.guiManager.pet.setEnabled(true); checkInvisibility(); checkDangerous(); @@ -72,28 +89,30 @@ public void tick() { } } + private boolean checkDangerousAndCurrentMap() { + safety.setRefreshing(System.currentTimeMillis() <= refreshing); + return safety.tick() && checkMap(); + } - private boolean checkCurrentMap() { - boolean mapWrong = config.WORKING_MAP != hero.map.id; - - if (mapWrong) { - - hero.runMode(); - - main.setModule(new MapModule()) - .setTargetAndBack(main.starManager.fromId(main.config.WORKING_MAP)); - + private boolean checkMap() { + if (this.config.GENERAL.WORKING_MAP != this.hero.map.id && !main.mapManager.entities.portals.isEmpty()) { + this.main.setModule(new MapModule()) + .setTarget(this.main.starManager.byId(this.main.config.GENERAL.WORKING_MAP)); return false; } - return true; } - boolean isNotWaiting() { - return System.currentTimeMillis() > waiting; + public boolean isNotWaiting() { + /*if (!current.isCollected() && main.statsManager.currentBox == current.address) { + current.setCollected(); + waiting = System.currentTimeMillis() + current.boxInfo.waitTime + + hero.timeTo(hero.locationInfo.distance(current)) + 30; + }*/ + return System.currentTimeMillis() > waiting || current == null || current.removed; } - boolean tryCollectNearestBox() { + public boolean tryCollectNearestBox() { if (current != null) { collectBox(); @@ -106,23 +125,24 @@ boolean tryCollectNearestBox() { private void collectBox() { double distance = hero.locationInfo.distance(current); - if (distance < 600) { - - current.clickable.setRadius(1200); - drive.clickCenter(1); + if (distance < 200) { + drive.stop(false); + current.clickable.setRadius(800); + drive.clickCenter(true, current.locationInfo.now); current.clickable.setRadius(0); - current.setCollected(true); - - waiting = System.currentTimeMillis() + current.boxInfo.waitTime + hero.timeTo(distance) + 30; + current.setCollected(); + waiting = System.currentTimeMillis() + current.boxInfo.waitTime + + Math.min(1_000, current.getRetries() * 100) // Add 100ms per retry, max 1 second + + hero.timeTo(distance) + 30; } else { drive.move(current); } } private void checkDangerous() { - if (config.STAY_AWAY_FROM_ENEMIES) { + if (config.COLLECT.STAY_AWAY_FROM_ENEMIES) { Location dangerous = findClosestEnemyAndAddToDangerousList(); @@ -130,13 +150,12 @@ private void checkDangerous() { } } - private void checkInvisibility() { - if (config.AUTO_CLOACK + public void checkInvisibility() { + if (config.COLLECT.AUTO_CLOACK && !hero.invisible - && System.currentTimeMillis() - invisibleTime > 60000 - ) { + && System.currentTimeMillis() - invisibleTime > 60000) { invisibleTime = System.currentTimeMillis(); - API.keyboardClick(config.AUTO_CLOACK_KEY); + API.keyboardClick(config.COLLECT.AUTO_CLOACK_KEY); } } @@ -166,32 +185,22 @@ private void stayAwayFromLocation(Location awayLocation) { drive.move(target); } - void findBox() { - LocationInfo locationInfo = hero.locationInfo; - double distance = 100_000; - Box closest = null; + public void findBox() { + LocationInfo heroLoc = hero.locationInfo; - for (Box box : boxes) { - if (canCollect(box)) { - double distanceCurrent = locationInfo.distance(box.locationInfo); - if (distanceCurrent < distance) { - distance = distanceCurrent; - closest = box; - } - } - } - - if (current == null || current.isCollected() || closest != null && isBetter(closest)) { - current = closest; - } else { - current = null; - } + Box best = boxes + .stream() + .filter(this::canCollect) + .min(Comparator.comparingInt(b -> b.boxInfo.priority) + .thenComparingDouble(heroLoc::distance)).orElse(null); + this.current = current == null || best == null || current.isCollected() || isBetter(best) ? best : current; } private boolean canCollect(Box box) { return box.boxInfo.collect && !box.isCollected() - && (drive.canMove(box.locationInfo.now)); + && drive.canMove(box.locationInfo.now) + && (!box.type.equals("FROM_SHIP") || main.statsManager.deposit < main.statsManager.depositTotal); } private Location findClosestEnemyAndAddToDangerousList() { @@ -203,7 +212,7 @@ private Location findClosestEnemyAndAddToDangerousList() { if (ship.isInTimer()) { return ship.locationInfo.now; } else if (ship.isAttacking(hero)) { - ship.setTimerTo(400_000); + ship.setTimerTo(config.GENERAL.RUNNING.REMEMBER_ENEMIES_FOR * 1000); return ship.locationInfo.now; } diff --git a/src/main/java/com/github/manolo8/darkbot/modules/DisconnectModule.java b/src/main/java/com/github/manolo8/darkbot/modules/DisconnectModule.java new file mode 100644 index 00000000..4b6a7804 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/DisconnectModule.java @@ -0,0 +1,109 @@ +package com.github.manolo8.darkbot.modules; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.objects.Gui; +import com.github.manolo8.darkbot.modules.utils.SafetyFinder; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.Time; + +public class DisconnectModule extends TemporalModule { + + private final Long pauseTime; + private final String reason; + + private Main main; + private HeroManager hero; + private SafetyFinder safety; + + private Gui lostConnection; + private Gui logout; + private long logoutStart; + + private Long pauseUntil = null; + private boolean refreshing = false; + + /** + * @param pauseTime null for infinite pause, otherwise pause for that amount of MS. + */ + public DisconnectModule(Long pauseTime, String reason) { + this.reason = reason; + this.pauseTime = pauseTime; + } + + @Override + public void install(Main main) { + super.install(main); + this.main = main; + this.hero = main.hero; + this.safety = new SafetyFinder(main); + + this.lostConnection = main.guiManager.lostConnection; + this.logout = main.guiManager.logout; + } + + @Override + public void uninstall() { + safety.uninstall(); + } + + @Override + public boolean canRefresh() { + return false; + } + + @Override + public void tick() { + // Just in case refresh was super quick, don't go back to normal tick. + if (refreshing) { + tickStopped(); + return; + } + main.guiManager.pet.setEnabled(false); + safety.setRefreshing(true); + safety.tick(); + if (hero.locationInfo.isMoving() || safety.state() != SafetyFinder.Escaping.WAITING) return; + if (!logout.visible) logoutStart = System.currentTimeMillis(); + logout.show(true); + // Prevent bug where logout gets to 0 and doesn't log out, just force a reload + if (System.currentTimeMillis() - logoutStart > 25_000) { + logoutStart = System.currentTimeMillis() + 90_000; + System.out.println("Disconnect module, refreshing due to logout not finishing bug."); + Main.API.handleRefresh(); + } + } + + @Override + public void tickStopped() { + if (main.isRunning()) { + if (!lostConnection.visible) return; + // Bot done. Pause "forever" (unless a behaviour restarts it). + if (pauseTime == null) main.setRunning(false); + else if (pauseTime == 0) goBack(); + else { + pauseUntil = System.currentTimeMillis() + pauseTime; + main.setRunning(false); + } + } else if (pauseUntil != null && System.currentTimeMillis() > pauseUntil - 10_000) { + if (!refreshing) { + System.out.println("Disconnect module, refreshing after pause, getting back to work."); + Main.API.handleRefresh(); + refreshing = true; + } else if (System.currentTimeMillis() > pauseUntil) { + goBack(); + main.setRunning(true); + } + } + } + + @Override + public String status() { + return I18n.get("module.disconnect.status", reason, safety.status()); + } + + @Override + public String stoppedStatus() { + if (pauseUntil == null) return I18n.get("module.disconnect.status_stopped", reason); + else return I18n.get("module.disconnect.status_paused", reason, Time.toString(Math.max(0, pauseUntil - System.currentTimeMillis()))); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/DummyModule.java b/src/main/java/com/github/manolo8/darkbot/modules/DummyModule.java new file mode 100644 index 00000000..0b350df4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/DummyModule.java @@ -0,0 +1,23 @@ +package com.github.manolo8.darkbot.modules; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Module; + +public class DummyModule implements Module { + + @Override + public void install(Main main) {} + + @Override + public boolean canRefresh() { + return false; + } + + @Override + public void tick() {} + + @Override + public String status() { + return "Waiting module - Select a module in config"; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/GateModule.java b/src/main/java/com/github/manolo8/darkbot/modules/GateModule.java deleted file mode 100644 index 0a0b0f28..00000000 --- a/src/main/java/com/github/manolo8/darkbot/modules/GateModule.java +++ /dev/null @@ -1,166 +0,0 @@ -//package com.github.manolo8.darkbot.modules; -// -//import com.github.manolo8.darkbot.Main; -//import com.github.manolo8.darkbot.core.itf.Module; -//import com.github.manolo8.darkbot.core.entities.Npc; -//import com.github.manolo8.darkbot.core.manager.GuiManager; -//import com.github.manolo8.darkbot.core.manager.HeroManager; -//import com.github.manolo8.darkbot.core.manager.MapManager; -//import com.github.manolo8.darkbot.core.objects.LocationInfo; -// -//import java.util.List; -// -//import static com.github.manolo8.darkbot.Main.API; -//import static java.lang.Math.cos; -//import static java.lang.StrictMath.sin; -// -// -//public class GateModule implements Module { -// -// private MapManager mapManager; -// private HeroManager hero; -// -// private List npcs; -// -// private long clickDelay; -// private long laserTime; -// private long locker; -// -// private Npc current; -// -// private boolean direction; -// private boolean repairing; -// -// private LocationInfo safe; -// -// public GateModule() { -// safe = new LocationInfo(8000, 5500); -// } -// -// @Override -// public void install(Main main) { -// npcs = main.mapManager.entities.npcs; -// hero = main.hero; -// mapManager = main.mapManager; -// } -// -// @Override -// public boolean canRefresh() { -// return false; -// } -// -// @Override -// public void tick() { -// -// if (isGGMap()) { -// Npc closest = closest(hero.point); -// -// if (closest != null) { -// -// LocationInfo locationHero = hero.point; -// LocationInfo locationNpc = closest.point; -// -// if (hero.health.hpPercent() < 0.5 || repairing) { -// -// if (!repairing) repairing = true; -// else repairing = (hero.health.hpPercent() < 0.8); -// -// -// hero.runMode(); -// move(safe, 500, 1000); -// -// } else { -// -// hero.attackMode(); -// -// if (closest.health.hpPercent() < 0.25 && System.currentTimeMillis() - locker > 5000) { -// direction = !direction; -// locker = System.currentTimeMillis(); -// } -// -// if (locationNpc.isMoving() || locationNpc.distance(locationHero) > 800) { -// move(locationNpc, hero.shipInfo.speed * 0.625, closest.npcInfo.radius + locationNpc.measureSpeed() * 0.625); -// } -// -// if (current == null -// || current.removed -// || locationHero.distance(current) - locationHero.distance(locationNpc) > 150) { -// current = closest; -// } -// -// tickNormalMode(); -// } -// -// } else { -// hero.move(9000, 6500); -// } -// } -// } -// -// private boolean isGGMap() { -// int id = hero.map.id; -// return id == 51 || id == 52 || id == 53; -// } -// -// private void tickNormalMode() { -// if (mapManager.isTarget(current)) { -// -// if (!hero.isAttacking(current) && System.currentTimeMillis() - laserTime > 1000) { -// API.keyboardClick('Z'); -// -// laserTime = System.currentTimeMillis(); -// } -// -// } else if (hero.point.distance(current) < 800 && System.currentTimeMillis() - clickDelay > 500) { -// hero.setTarget(current); -// -// current.clickable.setRadius(800); -// hero.clickCenter(1); -// current.clickable.setRadius(0); -// API.keyboardClick('Z'); -// -// clickDelay = System.currentTimeMillis(); -// } -// } -// -// private void move(LocationInfo point, double distance, double radius) { -// -// LocationInfo current = hero.point; -// -// double angle = point.angle(current); -// -// -// LocationInfo target = new LocationInfo( -// point.x - cos(angle) * radius, -// point.y - sin(angle) * radius -// ); -// -// if (distance - target.distance(current) > 0) { -// double move = distance / radius; -// angle += direction ? move : -move; -// target.x = point.x - cos(angle) * radius; -// target.y = point.y - sin(angle) * radius; -// } -// -// hero.move(target); -// } -// -// private boolean hasNPC() { -// return !npcs.hasLineOfSight(); -// } -// -// private Npc closest(LocationInfo point) { -// double distance = -1; -// Npc closest = null; -// -// for (Npc npc : npcs) { -// double distanceCurrent = point.distance(npc.point); -// if (distance == -1 || distanceCurrent < distance) { -// distance = distanceCurrent; -// closest = npc; -// } -// } -// -// return closest; -// } -//} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/LootModule.java b/src/main/java/com/github/manolo8/darkbot/modules/LootModule.java index e93fa596..257af92c 100644 --- a/src/main/java/com/github/manolo8/darkbot/modules/LootModule.java +++ b/src/main/java/com/github/manolo8/darkbot/modules/LootModule.java @@ -2,24 +2,24 @@ import com.github.manolo8.darkbot.Main; import com.github.manolo8.darkbot.config.Config; -import com.github.manolo8.darkbot.config.NpcInfo; +import com.github.manolo8.darkbot.config.NpcExtra; import com.github.manolo8.darkbot.core.entities.Npc; -import com.github.manolo8.darkbot.core.entities.Portal; import com.github.manolo8.darkbot.core.entities.Ship; import com.github.manolo8.darkbot.core.itf.Module; import com.github.manolo8.darkbot.core.manager.HeroManager; -import com.github.manolo8.darkbot.core.objects.LocationInfo; import com.github.manolo8.darkbot.core.utils.Drive; import com.github.manolo8.darkbot.core.utils.Location; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.modules.utils.NpcAttacker; +import com.github.manolo8.darkbot.modules.utils.SafetyFinder; +import java.util.Comparator; import java.util.List; -import static com.github.manolo8.darkbot.Main.API; -import static java.lang.Double.max; import static java.lang.Double.min; -import static java.lang.Math.cos; -import static java.lang.Math.sin; +import static java.lang.Math.random; +@Feature(name = "Npc Killer", description = "Npc-only module. Will never pick up resources.") public class LootModule implements Module { private Main main; @@ -32,21 +32,15 @@ public class LootModule implements Module { private Config config; - public Npc target; - - private int radiusFix; - - private long laserTime; - private long clickDelay; - private int times; - private boolean sab; - - private boolean locked; - private boolean repairing; + protected NpcAttacker attack; + protected SafetyFinder safety; + protected long refreshing; @Override public void install(Main main) { this.main = main; + this.attack = new NpcAttacker(main); + this.safety = new SafetyFinder(main); this.hero = main.hero; this.drive = main.hero.drive; @@ -56,272 +50,183 @@ public void install(Main main) { this.config = main.config; } + @Override + public void uninstall() { + safety.uninstall(); + } + + @Override + public String status() { + return safety.state() != SafetyFinder.Escaping.NONE ? safety.status() : + attack.hasTarget() ? attack.status() : "Roaming"; + } + @Override public boolean canRefresh() { - return target == null; + if (!attack.hasTarget()) refreshing = System.currentTimeMillis() + 10000; + return !attack.hasTarget() && safety.state() == SafetyFinder.Escaping.WAITING; } @Override public void tick() { - if (checkDangerousAndCurrentMap()) { + main.guiManager.pet.setEnabled(true); if (findTarget()) { moveToAnSafePosition(); - doKillTargetTick(); - } else if (!drive.isMoving()) { - drive.moveRandom(); - } - - } - - } - - void doKillTargetTick() { - if (main.mapManager.isTarget(target)) { - - if (main.mapManager.isCurrentTargetOwned()) { - - if (checkIfIsAttackingAndCanContinue()) { - checkSab(); - } - + ignoreInvalidTarget(); + attack.doKillTargetTick(); } else { - target.setTimerTo(90000); - target = null; + hero.roamMode(); + if (!drive.isMoving()) drive.moveRandom(); } - - } else { - setTargetAndTryStartLaserAttack(); } } - boolean checkDangerousAndCurrentMap() { - - if (config.WORKING_MAP != hero.map.id) { - - main.setModule(new MapModule()) - .setTargetAndBack(main.starManager.fromId(main.config.WORKING_MAP)); - - } else if ((repairing = repairing || hero.health.hpPercent() < config.REPAIR_HP) || hasEnemies()) { - - if (repairing) - repairing = hero.health.hpPercent() >= config.WAIT_HP; - - Portal portal = main.starManager.next(hero.map, hero.locationInfo, hero.map); - - if (portal.locationInfo.distance(hero) < 100) { - if (isUnderAttack()) - hero.jumpPortal(portal); - } else - drive.move(portal); - - } else - return true; - - hero.runMode(); - - return false; + protected boolean checkDangerousAndCurrentMap() { + safety.setRefreshing(System.currentTimeMillis() <= refreshing); + return safety.tick() && checkMap(); } - boolean findTarget() { - if (target == null || target.removed) { - target = closestNpc(hero.locationInfo.now); - locked = false; + protected boolean checkMap() { + if (this.config.GENERAL.WORKING_MAP != this.hero.map.id && !main.mapManager.entities.portals.isEmpty()) { + this.main.setModule(new MapModule()) + .setTarget(this.main.starManager.byId(this.main.config.GENERAL.WORKING_MAP)); + return false; } - - return target != null; + return true; } - private void checkSab() { - if (config.AUTO_SAB && hero.health.shieldPercent() < 0.6 && target.health.shield > 12000) { + protected boolean findTarget() { + return (attack.target = closestNpc(hero.locationInfo.now)) != null; + } - if (!sab) { - API.keyboardClick(config.AUTO_SAB_KEY); - sab = true; + protected void ignoreInvalidTarget() { + double closestDist = drive.closestDistance(attack.target.locationInfo.now); + if (!main.mapManager.isTarget(attack.target)) { + if (closestDist > 600) { + attack.target.setTimerTo(1000); + hero.setTarget(attack.target = null); } - - } else if (sab) { - API.keyboardClick(config.AMMO_KEY); - sab = false; + } else if (!(attack.target.npcInfo.extra.has(NpcExtra.IGNORE_OWNERSHIP) || main.mapManager.isCurrentTargetOwned()) + || attack.isBugged() + || (hero.locationInfo.distance(attack.target) > config.LOOT.NPC_DISTANCE_IGNORE) // Too far away from ship + || (closestDist > 650 && attack.target.health.hpPercent() > 0.90) // Too far into obstacle and full hp + || (closestDist > 500 && !attack.target.locationInfo.isMoving() // Inside obstacle, waiting & and regen shields + && (attack.target.health.shIncreasedIn(1000) || attack.target.health.shieldPercent() > 0.99))) { + attack.target.setTimerTo(5000); + hero.setTarget(attack.target = null); + } else if (attack.target.playerInfo.username.contains("Invoke") && attack.target.npcInfo.extra.has(NpcExtra.PASSIVE) + && attack.target == hero.target && !attack.castingAbility()) { + attack.target.setTimerTo(600_000); + hero.setTarget(attack.target = null); } } - private boolean checkIfIsAttackingAndCanContinue() { - - long laser = System.currentTimeMillis() - laserTime; - - boolean attacking = hero.isAttacking(target); - boolean bugged = (!target.health.isDecreasedIn(1000) && laser > 1000); - - if ((!attacking || bugged) && hero.locationInfo.distance(target) < 800 && laser > 1500 + times * 10000) { - setRadiusAndClick(2); - times++; - laserTime = System.currentTimeMillis(); + protected boolean backwards = false; + protected void moveToAnSafePosition() { + Npc target = attack.target; + Location direction = drive.movingTo(); + Location heroLoc = hero.locationInfo.now; + Location targetLoc = target.locationInfo.destinationInTime(400); + + double distance = heroLoc.distance(target.locationInfo.now); + double angle = targetLoc.angle(heroLoc); + double radius = target.npcInfo.radius; + boolean noCircle = target.npcInfo.extra.has(NpcExtra.NO_CIRCLE); + + radius = attack.modifyRadius(radius); + if (radius > 750) noCircle = false; + + double angleDiff; + if (noCircle) { + double dist = targetLoc.distance(direction); + double minRad = Math.max(0, Math.min(radius - 200, radius * 0.5)); + if (dist <= radius && dist >= minRad) { + setConfig(direction); + return; + } + distance = minRad + random() * (radius - minRad - 10); + angleDiff = (random() * 0.1) - 0.05; + } else { + double maxRadFix = target.npcInfo.radius / 2, + radiusFix = (int) Math.max(Math.min(radius - distance, maxRadFix), -maxRadFix); + distance = (radius += radiusFix); + // Moved distance + speed - distance to chosen radius same angle, divided by radius + angleDiff = Math.max((hero.shipInfo.speed * 0.625) + (min(200, target.locationInfo.speed) * 0.625) + - heroLoc.distance(Location.of(targetLoc, angle, radius)), 0) / radius; } + direction = getBestDir(targetLoc, angle, angleDiff, distance); - return true; - } + while (!drive.canMove(direction) && distance < 10000) + direction.toAngle(targetLoc, angle += backwards ? -0.3 : 0.3, distance += 2); + if (distance >= 10000) direction.toAngle(targetLoc, angle, 500); - private void setTargetAndTryStartLaserAttack() { - if (hero.locationInfo.distance(target) <= target.npcInfo.radius && System.currentTimeMillis() - clickDelay > 1000) { - - hero.setTarget(target); - - setRadiusAndClick(1); - API.keyboardClick(config.AMMO_KEY); - clickDelay = System.currentTimeMillis(); - locked = true; - times = 0; - - } else if (!locked) { - target = null; - } + setConfig(direction); + drive.move(direction); } - private void setRadiusAndClick(int times) { - target.clickable.setRadius(800); - - drive.clickCenter(times); - - target.clickable.setRadius(0); + protected Location getBestDir(Location targetLoc, double angle, double angleDiff, double distance) { + int iteration = 1; + double forwardScore = 0, backScore = 0; + do { + forwardScore += score(Location.of(targetLoc, angle + (angleDiff * iteration), distance)); + backScore += score(Location.of(targetLoc, angle - (angleDiff * iteration), distance)); + // Toggle direction if either one of the directions is perfect, or one is 300 better. + if (forwardScore < 0 != backScore < 0 || Math.abs(forwardScore - backScore) > 300) break; + } while (iteration++ < config.LOOT.MAX_CIRCLE_ITERATIONS); + + if (iteration <= config.LOOT.MAX_CIRCLE_ITERATIONS) backwards = backScore > forwardScore; + return Location.of(targetLoc, angle + angleDiff * (backwards ? -1 : 1), distance); } - void moveToAnSafePosition() { - - double hp = target.health.hpPercent(); - - if (hp == 1) { - drive.move(target); - } else { - - LocationInfo heroInfo = hero.locationInfo; - LocationInfo targetInfo = target.locationInfo; - - Location locationHero = heroInfo.now; - Location locationCurrent = targetInfo.destinationInTime(200); - - double radius = target.npcInfo.radius; - - double distance = heroInfo.now.distance(targetInfo.now); - double angle = locationCurrent.angle(locationHero); - - if (distance > radius) { - radiusFix -= (distance - radius) / 2; - radiusFix = (int) max(radiusFix, -target.npcInfo.radius / 2); - } else { - radiusFix += (radius - distance) / 6; - radiusFix = (int) min(radiusFix, target.npcInfo.radius / 2); - } - - if (distance > 2 * radius && target.health.hpPercent() < 0.25) { - hero.runMode(); - } else { - hero.attackMode(); - } - - radius += radiusFix; - - double speed = min(200, target.locationInfo.speed) * 0.625; - double moveDistance = hero.shipInfo.speed * 0.625 + speed; - - double x = locationCurrent.x - cos(angle) * radius; - double y = locationCurrent.y - sin(angle) * radius; - - boolean circle = (moveDistance = (moveDistance - (locationHero.distance(x, y)))) > 0; - - if (circle) { - double add = moveDistance / radius; - - angle += add; - - x = locationCurrent.x - cos(angle) * radius; - y = locationCurrent.y - sin(angle) * radius; - } - - drive.move(x, y); - - - } + protected double score(Location loc) { + return (drive.canMove(loc) ? 0 : -1000) - npcs.stream() // Consider barrier as bad as 1000 radius units. + .filter(n -> attack.target != n) + .mapToDouble(n -> Math.max(0, n.npcInfo.radius - n.locationInfo.now.distance(loc))) + .sum(); } - private boolean isUnderAttack() { - for (Ship ship : ships) { - if (ship.playerInfo.isEnemy()) { - - if (ship.isAttacking(hero)) { - return true; - } - - } - } - return false; + protected void setConfig(Location direction) { + if (!attack.hasTarget()) hero.roamMode(); + else if (config.LOOT.RUN_CONFIG_IN_CIRCLE + && attack.target.health.hpPercent() < 0.25 + && hero.locationInfo.now.distance(direction) > attack.target.npcInfo.radius * 2) hero.runMode(); + else if (hero.locationInfo.now.distance(direction) > attack.target.npcInfo.radius * 3) hero.roamMode(); + else hero.attackMode(attack.target); } - private boolean hasEnemies() { - - if (config.RUN_FROM_ENEMIES || config.RUN_FROM_ENEMIES_IN_SIGHT) { - for (Ship ship : ships) { - if (ship.playerInfo.isEnemy()) { - - if (config.RUN_FROM_ENEMIES_IN_SIGHT) - return true; - - if (ship.isAttacking(hero)) { - ship.setTimerTo(400_000); - return true; - } else if (ship.isInTimer()) { - return true; - } - - } - } + protected boolean isAttackedByOthers(Npc npc) { + for (Ship ship : this.ships) { + if (ship.address == hero.address || ship.address == hero.pet.address + || !ship.isAttacking(npc)) continue; + if (!npc.npcInfo.extra.has(NpcExtra.IGNORE_ATTACKED)) npc.setTimerTo(20_000); + return true; } - return false; } - private boolean isAttackedByOthers(Npc npc) { - - for (Ship ship : ships) { - if (ship.isAttacking(npc)) { - npc.setTimerTo(90000); - return true; - } - } - - return false; + protected Npc closestNpc(Location location) { + int extraPriority = attack.hasTarget() && + (hero.target == attack.target || hero.locationInfo.distance(attack.target) < 600) + ? 20 - (int)(attack.target.health.hpPercent() * 10) : 0; + return this.npcs.stream() + .filter(n -> (n == attack.target && hero.isAttacking(attack.target)) || + ((!config.GENERAL.ROAMING.ONLY_KILL_PREFERRED || main.mapManager.preferred.contains(n.locationInfo.now)) + && shouldKill(n) + && drive.closestDistance(n.locationInfo.now) < 500)) + .min(Comparator.comparingInt(n -> n.npcInfo.priority - (n == attack.target ? extraPriority : 0)) + .thenComparing(n -> n.health.hpPercent()) + .thenComparing(n -> n.locationInfo.now.distance(location))).orElse(null); } - private Npc closestNpc(Location location) { - - double distance = 0; - int priority = 0; - - Npc maxPriority = null; - Npc minDistance = null; - - for (Npc npc : npcs) { - - if (npc.npcInfo.kill && !npc.isInTimer() && !isAttackedByOthers(npc)) { - - double distanceCurrent = npc.locationInfo.now.distance(location); - int priorityCurrent = npc.npcInfo.priority; - - if (minDistance == null || distanceCurrent < distance) { - distance = distanceCurrent; - minDistance = npc; - } - - if (maxPriority == null || priorityCurrent >= priority) { - priority = priorityCurrent; - maxPriority = npc; - } - - } - } - - return minDistance == null ? null : priority > minDistance.npcInfo.priority ? maxPriority : minDistance; + protected boolean shouldKill(Npc n) { + boolean attacked = this.isAttackedByOthers(n); + return n.npcInfo.kill && !n.isInTimer() && + (n.npcInfo.extra.has(NpcExtra.IGNORE_ATTACKED) || !attacked) && // Either ignore attacked, or not being attacked + (!n.npcInfo.extra.has(NpcExtra.ATTACK_SECOND) || attacked) && // Either don't want to attack second, or being attacked + (n.playerInfo.username.contains("Invoke") || !n.npcInfo.extra.has(NpcExtra.PASSIVE) || n.isAttacking(hero)); } + } diff --git a/src/main/java/com/github/manolo8/darkbot/modules/LootNCollectorModule.java b/src/main/java/com/github/manolo8/darkbot/modules/LootNCollectorModule.java index f857951a..f139c561 100644 --- a/src/main/java/com/github/manolo8/darkbot/modules/LootNCollectorModule.java +++ b/src/main/java/com/github/manolo8/darkbot/modules/LootNCollectorModule.java @@ -1,31 +1,40 @@ package com.github.manolo8.darkbot.modules; import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; import com.github.manolo8.darkbot.core.entities.Box; import com.github.manolo8.darkbot.core.itf.Module; import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.manager.PetManager; import com.github.manolo8.darkbot.core.utils.Drive; +import com.github.manolo8.darkbot.extensions.features.Feature; +import com.github.manolo8.darkbot.utils.I18n; +@Feature(name = "Kill & Collect", description = "Kills npcs and collects resources at the same time.") public class LootNCollectorModule implements Module { - private final LootModule lootModule; - private final CollectorModule collectorModule; + protected LootModule lootModule = new LootModule(); + protected CollectorModule collectorModule = new CollectorModule(); - private HeroManager hero; - private Drive drive; - - public LootNCollectorModule() { - this.lootModule = new LootModule(); - this.collectorModule = new CollectorModule(); - } + protected PetManager pet; + protected HeroManager hero; + protected Drive drive; + protected Config config; @Override public void install(Main main) { lootModule.install(main); collectorModule.install(main); + this.pet = main.guiManager.pet; this.hero = main.hero; this.drive = main.hero.drive; + this.config = main.config; + } + + @Override + public String status() { + return I18n.get("module.kill_n_collect.status", lootModule.status(), collectorModule.status()); } @Override @@ -40,33 +49,32 @@ public boolean canRefresh() { @Override public void tick() { + if (collectorModule.isNotWaiting() && lootModule.checkDangerousAndCurrentMap()) { + pet.setEnabled(true); - if (collectorModule.isNotWaiting()) { + if (lootModule.findTarget()) { - if (lootModule.checkDangerousAndCurrentMap()) { + collectorModule.findBox(); - if (lootModule.findTarget()) { - - collectorModule.findBox(); - - Box box = collectorModule.current; - - if (box == null || box.locationInfo.distance(hero) > 600 || lootModule.target.health.hpPercent() < 0.25) { - lootModule.moveToAnSafePosition(); - } else { - collectorModule.tryCollectNearestBox(); - } - - lootModule.doKillTargetTick(); + Box box = collectorModule.current; + if (box == null || box.locationInfo.distance(hero) > config.COLLECT.RADIUS + || lootModule.attack.target.health.hpPercent() < 0.25) { + lootModule.moveToAnSafePosition(); } else { + lootModule.setConfig(collectorModule.current.locationInfo.now); + collectorModule.tryCollectNearestBox(); + } - collectorModule.findBox(); + lootModule.ignoreInvalidTarget(); + lootModule.attack.doKillTargetTick(); - if (!collectorModule.tryCollectNearestBox() && (!drive.isMoving() || drive.isOutOfMap())) { - drive.moveRandom(); - } + } else { + hero.roamMode(); + collectorModule.findBox(); + if (!collectorModule.tryCollectNearestBox() && (!drive.isMoving() || drive.isOutOfMap())) { + drive.moveRandom(); } } diff --git a/src/main/java/com/github/manolo8/darkbot/modules/MapModule.java b/src/main/java/com/github/manolo8/darkbot/modules/MapModule.java index 58700caa..6f6bf1e5 100644 --- a/src/main/java/com/github/manolo8/darkbot/modules/MapModule.java +++ b/src/main/java/com/github/manolo8/darkbot/modules/MapModule.java @@ -1,33 +1,30 @@ package com.github.manolo8.darkbot.modules; import com.github.manolo8.darkbot.Main; -import com.github.manolo8.darkbot.core.entities.Portal; -import com.github.manolo8.darkbot.core.itf.MapChange; -import com.github.manolo8.darkbot.core.itf.Module; -import com.github.manolo8.darkbot.core.manager.HeroManager; -import com.github.manolo8.darkbot.core.manager.StarManager; import com.github.manolo8.darkbot.core.objects.Map; -import com.github.manolo8.darkbot.core.utils.Drive; +import com.github.manolo8.darkbot.modules.utils.MapTraveler; +import com.github.manolo8.darkbot.utils.I18n; -public class MapModule implements Module, MapChange { +public class MapModule extends TemporalModule { + private MapTraveler traveler; - private Main main; - private HeroManager hero; - private Drive drive; - private StarManager star; + @Override + public void install(Main main) { + super.install(main); + if (traveler == null) this.traveler = new MapTraveler(main); + } - private Module back; - private Portal current; - private Map target; + public void uninstall() { + this.traveler.uninstall(); + this.traveler = null; + } @Override - public void install(Main main) { - this.hero = main.hero; - this.drive = main.hero.drive; - this.star = main.starManager; - this.main = main; - this.back = main.module; + public String status() { + return traveler.current != null ? + I18n.get("module.map_travel.status.has_next", traveler.target.name, traveler.current.target.name) : + I18n.get("module.map_travel.status.no_next", traveler.target.name); } @Override @@ -36,47 +33,13 @@ public boolean canRefresh() { } public void setTarget(Map target) { - this.target = target; - - current = star.next(hero.map, hero.locationInfo, target); - } - - public void setTargetAndBack(Map target) { - setTarget(target); + traveler.setTarget(target); } @Override public void tick() { - - if (current != null) { - - if (current.target != target) { - current = star.next(hero.map, hero.locationInfo, target); - } - - double distance = current.locationInfo.distance(hero); - - hero.runMode(); - - if (distance < 100 && !drive.isMoving()) { - hero.jumpPortal(current); - } else if (current.locationInfo.isLoaded()) { - drive.move(current); - } - } - + if (!traveler.isDone()) traveler.tick(); + if (traveler.isDone()) goBack(); } - @Override - public void onMapChange() { - if (hero.map == target) { - if (back != null) { - main.setModule(back); - back = null; - } - current = null; - } else { - current = star.next(hero.map, hero.locationInfo, target); - } - } } diff --git a/src/main/java/com/github/manolo8/darkbot/modules/TemporalModule.java b/src/main/java/com/github/manolo8/darkbot/modules/TemporalModule.java new file mode 100644 index 00000000..40baeac1 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/TemporalModule.java @@ -0,0 +1,22 @@ +package com.github.manolo8.darkbot.modules; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.itf.Module; + +public abstract class TemporalModule implements Module { + + private Main main; + private Module back; + + @Override + public void install(Main main) { + this.main = main; + this.back = main.module; + } + + protected void goBack() { + if (back != null) main.setModule(this.back); + back = null; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/utils/MapTraveler.java b/src/main/java/com/github/manolo8/darkbot/modules/utils/MapTraveler.java new file mode 100644 index 00000000..72ed7c63 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/utils/MapTraveler.java @@ -0,0 +1,100 @@ +package com.github.manolo8.darkbot.modules.utils; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.manager.StarManager; +import com.github.manolo8.darkbot.core.objects.Map; +import com.github.manolo8.darkbot.core.utils.Drive; +import com.github.manolo8.darkbot.core.utils.Location; + +import java.util.List; +import java.util.function.Consumer; + +public class MapTraveler { + + private Main main; + private HeroManager hero; + private List portals; + private Drive drive; + private StarManager star; + private Consumer listener = this::onMapChange; + private PortalJumper jumper; + + public Portal current; + public Map target; + private int lastPortals; + private long shipTpWait = -1, mapChangeWait = -1; + private boolean done; + + public MapTraveler(Main main) { + this.hero = main.hero; + this.portals = main.mapManager.entities.portals; + this.drive = main.hero.drive; + this.star = main.starManager; + this.main = main; + this.jumper = new PortalJumper(hero); + main.mapManager.mapChange.add(listener); + } + + public void uninstall() { + main.mapManager.mapChange.remove(listener); + } + + public void setTarget(Map target) { + shipTpWait = mapChangeWait = -1; + this.target = target; + this.done = false; + } + + public boolean isDone() { + return done; + } + + public void tick() { + if (hero.map == target) { + done = true; + return; + } + + if (hero.locationInfo.now.distance(hero.locationInfo.last) > 5000) + shipTpWait = System.currentTimeMillis() + 2000; + + if ((shipTpWait == -1) != (mapChangeWait == -1)) { + if (System.currentTimeMillis() < Math.max(shipTpWait, mapChangeWait)) return; + } + if (current == null || current.removed || lastPortals != portals.size()) { + current = star.next(hero, target); + lastPortals = portals.size(); + jumper.reset(); + } + + if (current == null) { + if (System.currentTimeMillis() - mapChangeWait > 3000) done = true; // No port found after 3 secs, just go back. + return; + } + shipTpWait = mapChangeWait = -1; + + if (current.locationInfo.distance(hero) > 1500) // Portal very close, no need to disable pet + main.guiManager.pet.setEnabled(false); + hero.runMode(); + + if (!moveToCurrent()) return; + jumper.jump(current); + } + + private boolean moveToCurrent() { + double leniency = Math.min(200 + drive.closestDistance(current.locationInfo.now), 600); + if (current.locationInfo.isLoaded() && drive.movingTo().distance(current.locationInfo.now) > leniency) { + drive.move(Location.of(current.locationInfo.now, Math.random() * Math.PI * 2, Math.random() * 200)); + return false; + } + return hero.locationInfo.distance(current) <= leniency && !drive.isMoving(); + } + + private void onMapChange(Map map) { + mapChangeWait = System.currentTimeMillis() + 2000; + lastPortals = -1; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/utils/NpcAttacker.java b/src/main/java/com/github/manolo8/darkbot/modules/utils/NpcAttacker.java new file mode 100644 index 00000000..deeb01ec --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/utils/NpcAttacker.java @@ -0,0 +1,131 @@ +package com.github.manolo8.darkbot.modules.utils; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.NpcExtra; +import com.github.manolo8.darkbot.core.DarkBotAPI; +import com.github.manolo8.darkbot.core.entities.FakeNpc; +import com.github.manolo8.darkbot.core.entities.Npc; +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.manager.MapManager; +import com.github.manolo8.darkbot.core.utils.Drive; + +import static com.github.manolo8.darkbot.Main.API; + +public class NpcAttacker { + + protected Main main; + protected MapManager mapManager; + protected HeroManager hero; + protected Drive drive; + + public Npc target; + protected Long ability; + + protected long laserTime; + protected long fixTimes; + protected long clickDelay; + protected long useRsbUntil; + protected boolean sab; + protected boolean rsb; + + public NpcAttacker(Main main) { + this.main = main; + this.mapManager = main.mapManager; + this.hero = main.hero; + this.drive = hero.drive; + } + + public String status() { + return target != null ? "Killing npc" + (hero.isAttacking(target) ? " S" : "") + (ability != null ? " A" : "") + (sab ? " SAB" : "") : "Idle"; + } + + public boolean castingAbility() { + return ability != null; + } + + public boolean hasTarget() { + return target != null && !target.removed; + } + + public boolean isBugged() { + return fixTimes > 5; + } + + public void doKillTargetTick() { + if (target == null || target instanceof FakeNpc) return; + if (!mapManager.isTarget(target)) { + lockAndSetTarget(); + return; + } + + if (ability != null && ability < System.currentTimeMillis()) { + if (target.health.maxHp < main.config.LOOT.SHIP_ABILITY_MIN) ability = null; + else if (hero.locationInfo.distance(target) < 575) { + API.keyboardClick(main.config.LOOT.SHIP_ABILITY); + ability = null; + } + } + + tryAttackOrFix(); + } + + void lockAndSetTarget() { + if (hero.locationInfo.distance(target) > 800 || System.currentTimeMillis() - clickDelay < 400) return; + hero.setTarget(target); + setRadiusAndClick(true); + clickDelay = System.currentTimeMillis(); + fixTimes = 0; + laserTime = clickDelay + 50; + if (main.config.LOOT.SHIP_ABILITY != null) ability = clickDelay + 4000; + } + + protected void tryAttackOrFix() { + boolean bugged = hero.isAttacking(target) + && (!hero.isAiming(target) || (!target.health.hpDecreasedIn(3000) && hero.locationInfo.distance(target) < 700)) + && System.currentTimeMillis() > (laserTime + fixTimes * 3000); + boolean ammoChanged = shouldSab() != sab || shouldRsb() != rsb; + if ((ammoChanged || !hero.isAttacking(target) || bugged) && System.currentTimeMillis() > laserTime) { + laserTime = System.currentTimeMillis() + 750; + if (!bugged || ammoChanged) API.keyboardClick(getAttackKey()); + else { + if (API instanceof DarkBotAPI) API.rawKeyboardClick((char) 0x11); + else setRadiusAndClick(false); + fixTimes++; + } + } + } + + public double modifyRadius(double radius) { + if (target.health.hpPercent() < 0.25 && target.npcInfo.extra.has(NpcExtra.AGGRESSIVE_FOLLOW)) radius *= 0.75; + if (target != hero.target || !hero.isAttacking(target) || castingAbility()) return Math.min(550, radius); + if (!target.locationInfo.isMoving() || target.health.hpPercent() < 0.25) return Math.min(600, radius); + return radius; + } + + private boolean shouldSab() { + return main.config.LOOT.SAB.ENABLED && hero.health.shieldPercent() < main.config.LOOT.SAB.PERCENT + && target.health.shield > main.config.LOOT.SAB.NPC_AMOUNT; + } + + private boolean shouldRsb() { + if (!main.config.LOOT.RSB.ENABLED || !target.npcInfo.extra.has(NpcExtra.USE_RSB)) return false; + if (useRsbUntil < System.currentTimeMillis() - main.config.LOOT.RSB.AMMO_REFRESH) useRsbUntil = System.currentTimeMillis(); + + return useRsbUntil > System.currentTimeMillis() - 50; + } + + private char getAttackKey() { + if (rsb = shouldRsb()) return main.config.LOOT.RSB.KEY; + if (sab = shouldSab()) return main.config.LOOT.SAB.KEY; + return this.target == null || this.target.npcInfo.attackKey == null ? + main.config.LOOT.AMMO_KEY : this.target.npcInfo.attackKey; + } + + private void setRadiusAndClick(boolean single) { + target.clickable.setRadius(800); + drive.clickCenter(single, target.locationInfo.now); + target.clickable.setRadius(0); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/utils/PetNpcAttacker.java b/src/main/java/com/github/manolo8/darkbot/modules/utils/PetNpcAttacker.java new file mode 100644 index 00000000..ab287b12 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/utils/PetNpcAttacker.java @@ -0,0 +1,54 @@ +package com.github.manolo8.darkbot.modules.utils; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.core.entities.FakeNpc; + +import static com.github.manolo8.darkbot.Main.API; + +public class PetNpcAttacker extends NpcAttacker { + + public PetNpcAttacker(Main main) { + super(main); + } + + public String status() { + return target != null ? "Killing npc" + (hero.isAttacking(target) ? " S" : "") + (ability != null ? " A" : "") + (sab ? " SAB" : "") : "Idle"; + } + + public boolean castingAbility() { + return ability != null; + } + + public boolean hasTarget() { + return target != null && !target.removed; + } + + public void doKillTargetTick() { + if (target == null || target instanceof FakeNpc) return; + if (!mapManager.isTarget(target)) { + lockAndSetTarget(); + return; + } + tryAttackOrFix(); + } + + protected void tryAttackOrFix() { + boolean bugged = !target.health.hpDecreasedIn(3000) && hero.locationInfo.distance(target) < 350 + && System.currentTimeMillis() > (5000 + laserTime + (fixTimes * 5000)); + if (!hero.pet.isAttacking(target) && System.currentTimeMillis() > laserTime) { + if (bugged) { + API.keyboardClick('E'); + fixTimes++; + } else if (hero.locationInfo.distance(target) > 800) { + API.keyboardClick(' '); + laserTime = System.currentTimeMillis() + 5000; + } + } + } + + @Override + public double modifyRadius(double radius) { + if (hero.target == target && !hero.pet.isAttacking(target) && System.currentTimeMillis() > laserTime) return 850; + return radius; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/utils/PortalJumper.java b/src/main/java/com/github/manolo8/darkbot/modules/utils/PortalJumper.java new file mode 100644 index 00000000..6da98abe --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/utils/PortalJumper.java @@ -0,0 +1,32 @@ +package com.github.manolo8.darkbot.modules.utils; + +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.manager.HeroManager; + +public class PortalJumper { + + private HeroManager hero; + private Portal last; + private long lastJumpStart; + + public PortalJumper(HeroManager hero) { + this.hero = hero; + } + + public void reset() { + this.last = null; + } + + public void jump(Portal target) { + hero.jumpPortal(target); + + if (target != last) { + last = target; + lastJumpStart = System.currentTimeMillis(); + } else if (System.currentTimeMillis() - lastJumpStart > 5_000) { + hero.drive.clickCenter(true, target.locationInfo.now); + lastJumpStart = System.currentTimeMillis(); + } + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/modules/utils/SafetyFinder.java b/src/main/java/com/github/manolo8/darkbot/modules/utils/SafetyFinder.java new file mode 100644 index 00000000..8784277e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/modules/utils/SafetyFinder.java @@ -0,0 +1,266 @@ +package com.github.manolo8.darkbot.modules.utils; + +import com.github.manolo8.darkbot.Main; +import com.github.manolo8.darkbot.config.Config; +import com.github.manolo8.darkbot.config.SafetyInfo; +import com.github.manolo8.darkbot.core.entities.BattleStation; +import com.github.manolo8.darkbot.core.entities.Portal; +import com.github.manolo8.darkbot.core.entities.Ship; +import com.github.manolo8.darkbot.core.manager.HeroManager; +import com.github.manolo8.darkbot.core.manager.MapManager; +import com.github.manolo8.darkbot.core.objects.Map; +import com.github.manolo8.darkbot.core.utils.Drive; +import com.github.manolo8.darkbot.core.utils.Location; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class SafetyFinder { + + private final Main main; + private Config.General.Safety SAFETY; + private Config.General.Running RUNNING; + + private final List ships; + private final HeroManager hero; + private final Drive drive; + private final MapTraveler mapTraveler; + private final PortalJumper jumper; + private final Consumer listener = this::onMapChange; + + private SafetyInfo safety; + private Escaping escape = Escaping.NONE; + private boolean refreshing; + private long escapingSince = -1; + private long lastTick; + + public enum Escaping { + ENEMY, SIGHT, REPAIR, REFRESH, WAITING, NONE; + boolean canUse(SafetyInfo safety) { + if (safety.type == SafetyInfo.Type.CBS) { + BattleStation cbs = ((BattleStation) safety.entity); + // Ignore enemy CBS, and if set to ALLY only, ignore empty meteorites (hull = 0) + if (cbs.info.isEnemy() || (cbs.hullId == 0 && safety.cbsMode == SafetyInfo.CbsMode.ALLY)) return false; + } + return safety.runMode.ordinal() <= this.ordinal(); + } + boolean shouldJump(SafetyInfo safety) { + if (safety.type != SafetyInfo.Type.PORTAL) return false; + return safety.jumpMode.ordinal() > this.ordinal(); + } + } + + private JumpState jumpState = JumpState.CURRENT_MAP; + private enum JumpState {CURRENT_MAP, JUMPING, JUMPED, RETURNING, RETURNED} + private Map prevMap; + + public SafetyFinder(Main main) { + this.main = main; + this.SAFETY = main.config.GENERAL.SAFETY; + this.RUNNING = main.config.GENERAL.RUNNING; + + this.ships = main.mapManager.entities.ships; + this.hero = main.hero; + this.drive = main.hero.drive; + this.mapTraveler = new MapTraveler(main); + this.jumper = new PortalJumper(hero); + this.main.mapManager.mapChange.add(listener); + } + + public void uninstall() { + mapTraveler.uninstall(); + this.main.mapManager.mapChange.remove(listener); + } + + private void onMapChange(Map map) { + if (safety != null && safety.type == SafetyInfo.Type.PORTAL) { + if (map == prevMap) jumpState = JumpState.RETURNED; + else if (jumpState == JumpState.JUMPING) jumpState = JumpState.JUMPED; + } + } + + public void setRefreshing(boolean refreshing) { + this.refreshing = refreshing; + } + + public Escaping state() { + return jumpState == JumpState.JUMPED && safety.jumpMode == SafetyInfo.JumpMode.ALWAYS_OTHER_SIDE ? + Escaping.WAITING : escape; + } + + public String status() { + return "Escaping " + simplify(escape) + (safety == null ? "" : " " + safety + + (safety.type == SafetyInfo.Type.PORTAL ? " " + simplify(jumpState) + (jumpState == JumpState.RETURNING ? " " + prevMap : "") : "")); + } + + private String simplify(Object obj) { + return obj.toString().toLowerCase().replace("_", " "); + } + + /** + * @return True if it's safe to keep working, false if the safety is working. + */ + public boolean tick() { + if (escape == Escaping.WAITING) { + if (escapingSince == -1) escapingSince = System.currentTimeMillis(); + + long escapeTime = System.currentTimeMillis() - escapingSince; + if (escapeTime > 121_000) escapingSince = -1; + if (escapeTime > 120_000) return false; // Over 2 min waiting? Try ticking module a bit to move. + } + // If no tick occurred for a while, means safety finder should have reset (Probably died) + if (System.currentTimeMillis() - lastTick > 2500) { + escape = Escaping.NONE; + jumpState = JumpState.CURRENT_MAP; + } + lastTick = System.currentTimeMillis(); + + if (jumpState == JumpState.CURRENT_MAP || jumpState == JumpState.JUMPING) { + activeTick(); + + if (escape == Escaping.NONE || safety == null) return true; + + if (hero.locationInfo.distance(safety.x, safety.y) > safety.radius()) { + moveToSafety(); + hero.runMode(); + return false; + } + } + + switch (jumpState) { + case CURRENT_MAP: + if (escape.shouldJump(safety) + // Also jump if taking damage & you would jump away from enemy. + || (hero.health.hpDecreasedIn(200) && Escaping.ENEMY.shouldJump(safety))) { + jumpState = JumpState.JUMPING; + drive.stop(false); + } + break; + case JUMPING: + prevMap = hero.map; + jumper.jump((Portal) safety.entity); + return false; + case JUMPED: + if (hero.health.hpDecreasedIn(100) || safety.jumpMode != SafetyInfo.JumpMode.ALWAYS_OTHER_SIDE + || (!refreshing && doneRepairing())) { + jumpState = JumpState.RETURNING; + mapTraveler.setTarget(prevMap); + } + break; + case RETURNING: + if (mapTraveler.isDone()) mapTraveler.setTarget(prevMap); + mapTraveler.tick(); + return false; + } + + if (jumpState == JumpState.CURRENT_MAP || jumpState == JumpState.RETURNED) { + escape = Escaping.WAITING; + + if (!refreshing && doneRepairing() && !hasEnemy()) { + escape = Escaping.NONE; + jumpState = JumpState.CURRENT_MAP; + return true; + } + } + return false; + } + + private void activeTick() { + Escaping oldEscape = escape; + escape = getEscape(); + if (escape == Escaping.NONE || escape == Escaping.WAITING) return; + + if (jumpState == JumpState.CURRENT_MAP && + (escape != oldEscape || safety == null || safety.entity == null || safety.entity.removed)) { + safety = getSafety(); + } + if (safety == null) { + escape = Escaping.NONE; + return; + } + + if (oldEscape != escape && escape == Escaping.ENEMY) castDefensiveAbility(); + } + + private Escaping getEscape() { + if (escape == Escaping.ENEMY || isUnderAttack()) return Escaping.ENEMY; + if (escape == Escaping.WAITING) return Escaping.WAITING; + if ((escape == Escaping.SIGHT && !RUNNING.STOP_RUNNING_NO_SIGHT) || hasEnemy()) return Escaping.SIGHT; + if (escape == Escaping.REPAIR || hero.health.hpPercent() < SAFETY.REPAIR_HP_RANGE.min || + (hero.health.hpPercent() < this.SAFETY.REPAIR_HP_NO_NPC && + (!hero.hasTarget() || hero.target.health.hpPercent() > 0.9))) return Escaping.REPAIR; + return refreshing ? Escaping.REFRESH : Escaping.NONE; + } + + private SafetyInfo getSafety() { + List safeties = main.mapManager.safeties.stream() + .filter(s -> s.entity != null && !s.entity.removed) + .filter(escape::canUse) + .peek(s -> s.distance = Math.max(0, drive.distanceBetween(hero.locationInfo.now, s.x, s.y) - s.radius())) + .sorted(Comparator.comparingDouble(s -> s.distance)) + .collect(Collectors.toList()); + if (safeties.isEmpty()) return null; + + SafetyInfo best = safeties.get(0); + + if (escape == Escaping.REPAIR || escape == Escaping.REFRESH || + RUNNING.RUN_FURTHEST_PORT == 0 || best.distance < RUNNING.RUN_FURTHEST_PORT) return best; + + List enemies = ships.stream().filter(this::runFrom).collect(Collectors.toList()); + + return safeties.stream() + .filter(s -> s.distance < enemies.stream() + .mapToDouble(enemy -> drive.distanceBetween(enemy.locationInfo.now, s.x, s.y)) + .min().orElse(Double.POSITIVE_INFINITY)) + .findFirst() + .orElse(best); + } + + private void moveToSafety() { + if ((jumpState != JumpState.CURRENT_MAP && jumpState != JumpState.JUMPING) + || drive.movingTo().distance(safety.x, safety.y) < safety.radius() + || safety.entity.removed) return; + Location safeLoc = new Location(safety.x, safety.y); + + double angle = safeLoc.angle(hero.locationInfo.now) + Math.random() * 0.2 - 0.1; + drive.move(Location.of(safeLoc, angle, -safety.radius() * (0.3 + (0.60 * Math.random())))); // 30%-90% radius + } + + private void castDefensiveAbility() { + Location to = drive.movingTo(); + if (drive.distanceBetween(hero.locationInfo.now, (int) to.x, (int) to.y) >= RUNNING.SHIP_ABILITY_MIN) { + Main.API.keyboardClick(RUNNING.SHIP_ABILITY); + } + } + + private boolean doneRepairing() { + if (!hero.isInMode(SAFETY.REPAIR) + && (hero.health.hpIncreasedIn(1000) || hero.health.hpPercent() == 1) + && (hero.health.shDecreasedIn(1000) || hero.health.shieldPercent() == 0)) hero.setMode(SAFETY.REPAIR); + return this.hero.health.shieldPercent() >= SAFETY.REPAIR_TO_SHIELD && + hero.setMode(SAFETY.REPAIR) && this.hero.health.hpPercent() >= SAFETY.REPAIR_HP_RANGE.max; + } + + private boolean isUnderAttack() { + if (!RUNNING.RUN_FROM_ENEMIES && !RUNNING.RUN_FROM_ENEMIES_SIGHT) return false; + return ships.stream().anyMatch(s -> s.playerInfo.isEnemy() && isAttackingOrTimer(s)); + } + + private boolean hasEnemy() { + if (!RUNNING.RUN_FROM_ENEMIES && !RUNNING.RUN_FROM_ENEMIES_SIGHT) return false; + return ships.stream().anyMatch(this::runFrom); + } + + private boolean runFrom(Ship ship) { + return ship.playerInfo.isEnemy() && (isAttackingOrTimer(ship) || + (RUNNING.RUN_FROM_ENEMIES_SIGHT && ship.locationInfo.distance(hero) < RUNNING.MAX_SIGHT_DISTANCE)); + } + + private boolean isAttackingOrTimer(Ship ship) { + if (ship.isAttacking(hero)) ship.setTimerTo(RUNNING.REMEMBER_ENEMIES_FOR * 1000); + return ship.isInTimer(); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/AuthAPI.java b/src/main/java/com/github/manolo8/darkbot/utils/AuthAPI.java new file mode 100644 index 00000000..1447a4a7 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/AuthAPI.java @@ -0,0 +1,48 @@ +package com.github.manolo8.darkbot.utils; + +import java.io.IOException; +import java.util.jar.JarFile; + +public interface AuthAPI { + + String VERIFIER_PATH = "lib/verifier.jar"; + AuthAPI INSTANCE = ReflectionUtils.createInstance("eu.darkbot.verifier.AuthAPIImpl", VERIFIER_PATH); + + static AuthAPI getInstance() { + return INSTANCE; + } + + /** + * Sets up initial auth. Some environments (like servers) may have + * less user friction by not requiring discord authentication. + */ + void setupAuth(); + + /** + * Returns if the user has been validly authenticated with discord + * @return true if auth was performed & valid, false otherwise. + */ + boolean isAuthenticated(); + + /** + * If the user didn't authenticate beforehand, it will prompt the user to authenticate. + * @return true if the user is a donor in the official darkbot discord server, false otherwise. + */ + boolean isDonor(); + + /** + * If the user didn't authenticate beforehand, it will prompt the user to authenticate. + * Will prompt the user to join the discord & donate if he hasn't done so yet. + * @return true if the user is a donor in the official darkbot discord server, false otherwise. + */ + boolean requireDonor(); + + /** + * Checks if a jar file has been signed by an authorized key + * @param jarFile The jar file to check + * @throws IOException If the file can't be found or read. + * @return true if signed & known signature, null if not signed, false if signed by untrusted key. + */ + Boolean checkPluginJarSignature(JarFile jarFile) throws IOException; + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/Base62.java b/src/main/java/com/github/manolo8/darkbot/utils/Base62.java new file mode 100644 index 00000000..245031d9 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/Base62.java @@ -0,0 +1,39 @@ +package com.github.manolo8.darkbot.utils; + +public class Base62 { + + public static final String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + public static final int BASE = ALPHABET.length(); + + private Base62() {} + + public static String encode(int i) { + StringBuilder sb = new StringBuilder(""); + if (i == 0) return "a"; + while (i > 0) i = encode(i, sb); + return sb.reverse().toString(); + } + + private static int encode(int i, final StringBuilder sb) { + int rem = i % BASE; + sb.append(ALPHABET.charAt(rem)); + return i / BASE; + } + + public static int decode(String str) { + return decode(new StringBuilder(str).reverse().toString().toCharArray()); + } + + private static int decode(char[] chars) { + int n = 0; + for (int i = chars.length - 1; i >= 0; i--) { + n += decode(ALPHABET.indexOf(chars[i]), i); + } + return n; + } + + private static int decode(int n, int pow) { + return n * (int) Math.pow(BASE, pow); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/Base64Utils.java b/src/main/java/com/github/manolo8/darkbot/utils/Base64Utils.java new file mode 100644 index 00000000..4f4f4f88 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/Base64Utils.java @@ -0,0 +1,43 @@ +package com.github.manolo8.darkbot.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class Base64Utils { + + @Deprecated public static String base64Encode(String text) { + return Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8)); + } + + @Deprecated public static String base64Decode(InputStream input) throws IOException { + return base64Decode(IOUtils.read(input)); + } + + @Deprecated public static String base64Decode(String text) { + return new String(Base64.getDecoder().decode(text), StandardCharsets.UTF_8); + } + + + public static String decode(InputStream input) throws IOException { + return decode(IOUtils.read(input)); + } + + public static String decode(String text) { + return new String(decodeBytes(text), StandardCharsets.UTF_8); + } + + public static byte[] decodeBytes(String text) { + return Base64.getDecoder().decode(text); + } + + public static String encode(String text) { + return encodeBytes(text.getBytes(StandardCharsets.UTF_8)); + } + + public static String encodeBytes(byte[] bytes) { + return Base64.getEncoder().encodeToString(bytes); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/Encryption.java b/src/main/java/com/github/manolo8/darkbot/utils/Encryption.java new file mode 100644 index 00000000..77114973 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/Encryption.java @@ -0,0 +1,37 @@ +package com.github.manolo8.darkbot.utils; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +public class Encryption { + + private static final String CIPHER = "AES/CBC/PKCS5Padding", KEY_FACTORY = "PBKDF2WithHmacSHA512"; + private static final int KEY_LENGTH = 128, ITERATIONS = 10000; + + public static SecretKeySpec createSecretKey(char[] password, byte[] salt) + throws NoSuchAlgorithmException, InvalidKeySpecException { + return new SecretKeySpec(SecretKeyFactory.getInstance(KEY_FACTORY) + .generateSecret(new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH)).getEncoded(), "AES"); + } + + public static String encrypt(String data, SecretKeySpec key) throws GeneralSecurityException { + Cipher pbeCipher = Cipher.getInstance(CIPHER); + pbeCipher.init(Cipher.ENCRYPT_MODE, key); + return Base64Utils.encodeBytes(pbeCipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV()) + ":" + + Base64Utils.encodeBytes(pbeCipher.doFinal(data.getBytes(StandardCharsets.UTF_8))); + } + + public static String decrypt(String data, SecretKeySpec key) throws GeneralSecurityException { + Cipher pbeCipher = Cipher.getInstance(CIPHER); + pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(Base64Utils.decodeBytes(data.split(":", 2)[0]))); + return new String(pbeCipher.doFinal(Base64Utils.decodeBytes(data.split(":", 2)[1])), StandardCharsets.UTF_8); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/I18n.java b/src/main/java/com/github/manolo8/darkbot/utils/I18n.java new file mode 100644 index 00000000..c598de75 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/I18n.java @@ -0,0 +1,78 @@ +package com.github.manolo8.darkbot.utils; + +import com.github.manolo8.darkbot.config.ConfigEntity; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class I18n { + + public static final List SUPPORTED_LOCALES = Stream.of( + "bg", "cs", "de", "el", "en", "es", "fr", "hu", "it", "pl", "pt", "ro", "ru", "tr", "uk" + ).map(Locale::new).sorted(Comparator.comparing(Locale::getDisplayName)).collect(Collectors.toList()); + private static final Properties props = new Properties(); + private static Locale locale; + static { + reloadProps(); + } + + public static void reloadProps() { + props.clear(); + locale = ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.LOCALE; + + loadResource(props, getLangFile(Locale.ENGLISH)); + if (!locale.equals(Locale.ENGLISH)) loadResource(props, getLangFile(locale)); + } + + public static Locale getLocale() { + return locale; + } + + private static URL getLangFile(Locale locale) { + URL res = I18n.class.getResource("/lang/strings_" + locale.toLanguageTag() + ".properties"); + if (res == null) System.out.println("Couldn't find translation file for " + locale); + return res; + } + + public static void loadResource(Properties props, URL resource) { + if (resource == null) return; + try { + props.load(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8)); + } catch (IOException e) { + System.err.println("Failed to load translations: " + resource); + e.printStackTrace(); + } + } + + private I18n() {} + + public static String get(String key) { + if (key == null) throw new IllegalArgumentException("Translation key must not be null"); + String res = (String) props.get(key); + return res != null ? res : "Missing " + key; + } + + public static String getOrDefault(String key, String fallback) { + if (key == null) return fallback; + String res = (String) props.get(key); + return res != null ? res : fallback; + } + + public static String get(String key, Object... arguments) { + return MessageFormat.format(get(key), arguments); + } + + public static String getOrDefault(String key, String fallback, Object... arguments) { + return MessageFormat.format(getOrDefault(key, fallback), arguments); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/IOUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/IOUtils.java new file mode 100644 index 00000000..68b4ff36 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/IOUtils.java @@ -0,0 +1,34 @@ +package com.github.manolo8.darkbot.utils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +public class IOUtils { + + public static void write(OutputStream output, String str) throws IOException { + output.write(str.getBytes()); + } + + public static String read(InputStream input) throws IOException { + return read(input, false); + } + + public static String read(InputStream input, boolean closeStream) throws IOException { + return new String(readByteArray(input, closeStream), StandardCharsets.UTF_8); + } + + public static byte[] readByteArray(InputStream input, boolean closeStream) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = input.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + if (closeStream) input.close(); + + return result.toByteArray(); + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/LogUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/LogUtils.java new file mode 100644 index 00000000..af58269b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/LogUtils.java @@ -0,0 +1,102 @@ +package com.github.manolo8.darkbot.utils; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +public class LogUtils { + public static final DateTimeFormatter LOG_DATE = DateTimeFormatter.ofPattern("uuuu/MM/dd HH:mm:ss.SSS"); + public static final DateTimeFormatter FILENAME_DATE = DateTimeFormatter.ofPattern("uuuu-MM-dd_HH-mm-ss_SSS"); + public static final Path LOG_FOLDER = Paths.get("logs"); + public static final String START_TIME = LocalDateTime.now().format(FILENAME_DATE); + + public static void setOutputToFile() { + if (!Files.exists(LOG_FOLDER)) createFolder(); + else removeOld(); + + try { + PrintStream output = getLogger(); + System.setOut(output); + System.setErr(output); + } catch (FileNotFoundException | UnsupportedEncodingException e) { + System.out.println("Failed to redirect logs, file not found:"); + e.printStackTrace(); + } + } + + public static OutputStream createLogFile(String filename) { + String fileName = "logs/" + filename + ".log"; + + try { + return new FileOutputStream(fileName, true); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + } + + private static PrintStream getLogger() throws FileNotFoundException, UnsupportedEncodingException { + return new PrintStreamWithDate("logs/" + START_TIME + ".log"); + } + + private static void createFolder() { + try { + Files.createDirectory(LOG_FOLDER); + } catch (IOException e) { + System.out.println("Failed to create log folder"); + e.printStackTrace(); + } + } + + private static void removeOld() { + try { + Instant keep = Instant.now().minus(24, ChronoUnit.HOURS); + Files.list(LOG_FOLDER) + .filter(p -> isBefore(p, keep)) + .forEach(LogUtils::tryDelete); + } catch (IOException e) { + System.out.println("Failed to remove old logs"); + e.printStackTrace(); + } + } + + private static boolean isBefore(Path path, Instant date) { + try { + return Files.getLastModifiedTime(path).toInstant().isBefore(date); + } catch (IOException e) { + System.out.println("Failed to determine log date, won't remove"); + e.printStackTrace(); + return false; + } + } + + private static void tryDelete(Path path) { + try { + Files.delete(path); + } catch (IOException e) { + System.out.println("Failed to delete log"); + e.printStackTrace(); + } + } + + private static class PrintStreamWithDate extends PrintStream { + public PrintStreamWithDate(String logfile) throws FileNotFoundException, UnsupportedEncodingException { + super(logfile, "UTF-8"); + } + + @Override + public void println(String string) { + super.println("[" + LocalDateTime.now().format(LOG_DATE) + "] " + string); + } + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/MathUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/MathUtils.java new file mode 100644 index 00000000..af2c01d8 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/MathUtils.java @@ -0,0 +1,12 @@ +package com.github.manolo8.darkbot.utils; + +public class MathUtils { + + private static final double TAU = Math.PI * 2; + + public static double angleDiff(double alpha, double beta) { + double phi = Math.abs(beta - alpha) % TAU; + return phi > Math.PI ? TAU - phi : phi; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/ReflectionUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/ReflectionUtils.java new file mode 100644 index 00000000..6ece4042 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/ReflectionUtils.java @@ -0,0 +1,111 @@ +package com.github.manolo8.darkbot.utils; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ReflectionUtils { + private ReflectionUtils() {}; + + /** A map from primitive types to their corresponding wrapper types. */ + private static final Map, Class> PRIMITIVE_TO_WRAPPER; + static { + Map, Class> primitiveToWrapper = new HashMap<>(16); + primitiveToWrapper.put(boolean.class, Boolean.class); + primitiveToWrapper.put(byte.class, Byte.class); + primitiveToWrapper.put(char.class, Character.class); + primitiveToWrapper.put(double.class, Double.class); + primitiveToWrapper.put(float.class, Float.class); + primitiveToWrapper.put(int.class, Integer.class); + primitiveToWrapper.put(long.class, Long.class); + primitiveToWrapper.put(short.class, Short.class); + primitiveToWrapper.put(void.class, Void.class); + + PRIMITIVE_TO_WRAPPER = Collections.unmodifiableMap(primitiveToWrapper); + } + + public static T createInstance(String className, String jarUrl) { + try { + URLClassLoader loader = new URLClassLoader(new URL[]{new File(jarUrl).toURI().toURL()}); + return createInstance((Class) loader.loadClass(className)); + } catch (MalformedURLException | ClassNotFoundException e) { + e.printStackTrace(); + throw new RuntimeException("Failed to find class: " + className); + } + } + + public static T createInstance(Class clazz) { + return createInstance(clazz, null, null); + } + + public static T createInstance(Class clazz, Class

paramTyp, P param) { + try { + if (paramTyp != null) { + try { + return clazz.getConstructor(paramTyp).newInstance(param); + } catch (NoSuchMethodException ignore) {} + } + return clazz.getConstructor().newInstance(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException("No default constructor found for " + clazz.getName()); + } catch (InvocationTargetException e) { + e.getCause().getStackTrace(); + throw new RuntimeException("Error creating instance of " + clazz.getName(), e.getCause()); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + throw new RuntimeException("Error creating instance of " + clazz.getName(), e); + } + } + + public static Object get(Field field, Object obj) { + try { + return field.get(obj); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static void set(Field field, Object obj, Object value) { + try { + field.set(obj, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Class wrapped(Class type) { + if (!type.isPrimitive()) return type; + //noinspection unchecked + return (Class) PRIMITIVE_TO_WRAPPER.get(type); + } + + public static Type[] findGenericParameters(Class clazz, Class generic) { + Type[] params; + for (Type itf : clazz.getGenericInterfaces()) { + if ((params = getTypes(itf, generic)) != null) return params; + } + if ((params = getTypes(clazz.getGenericSuperclass(), generic)) != null) return params; + + Class parent = clazz.getSuperclass(); + if (parent != null) return findGenericParameters(generic, parent); + return null; + } + + private static Type[] getTypes(Type type, Class expected) { + if (!(type instanceof ParameterizedType)) return null; + ParameterizedType paramType = (ParameterizedType) type; + if (paramType.getRawType() == expected) return paramType.getActualTypeArguments(); + return null; + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/StringQuery.java b/src/main/java/com/github/manolo8/darkbot/utils/StringQuery.java new file mode 100644 index 00000000..4fbb7869 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/StringQuery.java @@ -0,0 +1,31 @@ +package com.github.manolo8.darkbot.utils; + +import java.util.Arrays; +import java.util.Locale; + +/** + * Represents a search over text + */ +public class StringQuery { + private boolean matchCase = false; // If it should match case + private boolean wordStart = true; // If it should match word start + private boolean words = true; // If the search should split and search each word individually + public String query; + + public boolean matches(final String toStr) { + if (query == null || query.isEmpty()) return true; + if (words && query.contains(" ")) return Arrays.stream(query.split(" ")).allMatch(q -> matches(toStr, q)); + else return matches(toStr, query); + } + + public boolean matches(String toStr, String query) { + if (query == null || query.isEmpty()) return true; + if (!matchCase) { + toStr = toStr.toLowerCase(Locale.ROOT); + query = query.toLowerCase(Locale.ROOT); + } + if (wordStart) return toStr.startsWith(query) || toStr.contains(" " + query); + else return toStr.contains(query); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/SystemUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/SystemUtils.java new file mode 100644 index 00000000..e8260368 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/SystemUtils.java @@ -0,0 +1,27 @@ +package com.github.manolo8.darkbot.utils; + +import java.awt.*; +import java.awt.datatransfer.StringSelection; +import java.net.URI; + +public class SystemUtils { + + public static void openUrl(String url) { + openUrl(URI.create(url)); + } + + public static void openUrl(URI url) { + try { + Desktop.getDesktop().browse(url); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void toClipboard(String str) { + if (str == null || str.isEmpty()) return; + StringSelection selection = new StringSelection(str); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/ThrowBiFunction.java b/src/main/java/com/github/manolo8/darkbot/utils/ThrowBiFunction.java new file mode 100644 index 00000000..e81469b5 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/ThrowBiFunction.java @@ -0,0 +1,19 @@ +package com.github.manolo8.darkbot.utils; + +/** + * Similar to {@link java.util.function.BiFunction} but with Throwable + * @see ThrowFunction + */ +@FunctionalInterface +public interface ThrowBiFunction { + + /** + * Applies this function to the given arguments. + * + * @param t the first function argument + * @param u the second function argument + * @return the function result + * @throws X throwable of function + */ + R apply(T t, U u) throws X; +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/utils/ThrowFunction.java b/src/main/java/com/github/manolo8/darkbot/utils/ThrowFunction.java new file mode 100644 index 00000000..acb9554a --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/ThrowFunction.java @@ -0,0 +1,17 @@ +package com.github.manolo8.darkbot.utils; + +/** + * Similar to {@link java.util.function.Function} but with Throwable + */ +@FunctionalInterface +public interface ThrowFunction { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + * @throws X function exception + */ + R apply(T t) throws X; +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/utils/Time.java b/src/main/java/com/github/manolo8/darkbot/utils/Time.java new file mode 100644 index 00000000..d79a7d75 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/Time.java @@ -0,0 +1,67 @@ +package com.github.manolo8.darkbot.utils; + +public class Time { + public static final int SECOND = 1000, MINUTE = SECOND * 60, HOUR = MINUTE * 60, DAY = HOUR * 24, WEEK = DAY * 7; + + public static String toString(Integer time) { + if (time == null) return "-"; + return millisToString(time.longValue()); + } + + public static String toString(long time) { + return millisToString(time); + } + + public static String millisToString(long millis) { + return secondsToString((int) (millis / 1000L)); + } + + public static String secondsToString(int seconds) { + StringBuilder builder = new StringBuilder(); + if (seconds >= 3600) { + int hours = seconds / 3600; + if (hours < 10) { + builder.append('0'); + } + builder.append(hours).append(':'); + } + if (seconds >= 60) { + int minutes = seconds % 3600 / 60; + if (minutes < 10) { + builder.append('0'); + } + builder.append(minutes).append(':'); + } + if ((seconds %= 60) < 10) { + builder.append('0'); + } + builder.append(seconds); + return builder.toString(); + } + + /** + * Seconds to short-string value + */ + public static String secondsToShort(double time) { + if (time < 60) return Math.round(time) + "s"; + if ((time /= 60) < 60) return Math.round(time) + "m"; + if ((time /= 60) < 60) return Math.round(time) + "h"; + if ((time /= 24) < 100) return Math.round(time) + "d"; + if ((time /= 7) < 100) return Math.round(time) + "w"; + if ((time = time * 7 / 365) < 100) return Math.round(time) + "y"; + return "∞ "; // Over 99 years + } + + public static void sleepMax(long time, int total) { + time = System.currentTimeMillis() - time; + sleep(total - time); + } + + public static void sleep(long millis) { + if (millis <= 0) return; + try { + Thread.sleep(millis); + } catch (InterruptedException ignore) {} + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/XmlHelper.java b/src/main/java/com/github/manolo8/darkbot/utils/XmlHelper.java new file mode 100644 index 00000000..2ad89bde --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/XmlHelper.java @@ -0,0 +1,37 @@ +package com.github.manolo8.darkbot.utils; + +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class XmlHelper { + + public static boolean hasAnyElement(Element e, String name) { + return e.getElementsByTagName(name).getLength() > 0; + } + + public static Element getElement(Element e, String name) { + return (Element) e.getElementsByTagName(name).item(0); + } + + public static Integer attrToInt(Element e, String attr) { + String value = e.getAttribute(attr); + return value == null || value.isEmpty() ? null : Integer.parseInt(value); + } + + public static Integer valueToInt(Element e, String name) { + return valueToInt(getElement(e, name)); + } + + public static Integer valueToInt(Element e) { + String value = e.getTextContent(); + return value == null || value.isEmpty() ? null : Integer.parseInt(value); + } + + public static Stream stream(NodeList list) { + return IntStream.range(0, list.getLength()).mapToObj(i -> (Element) list.item(i)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/utils/debug/ReadObjNames.java b/src/main/java/com/github/manolo8/darkbot/utils/debug/ReadObjNames.java new file mode 100644 index 00000000..75271fc4 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/debug/ReadObjNames.java @@ -0,0 +1,82 @@ +package com.github.manolo8.darkbot.utils.debug; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.github.manolo8.darkbot.Main.API; + +/** + * Reads object names from memory. + * Also reads strings on that address. + */ +public class ReadObjNames { + private static final String FILE_NAME = "replacements.txt"; + private static final File REPLACEMENTS_FILE = getReplacementsFile(); + private static Map replacements = new HashMap<>(); + private static long lastModified; + + public static void of(long address) { + of(address, 0x400); + } + + public static void of(long address, int maxOffset) { + System.out.println("==========BEGIN==========\n"); + updateReplacements(); + + for (int offset = 0; offset < maxOffset; offset++) { + long addr = API.readMemoryLong(address + offset); + //prints string at offset + print(addr, offset, false); + + if (isInvalid(addr)) continue; + addr = API.readMemoryLong(addr + 0x10); + if (isInvalid(addr)) continue; + addr = API.readMemoryLong(addr + 0x28); + if (isInvalid(addr)) continue; + + //prints object name at offset + print(API.readMemoryLong(addr + 0x90), offset, true); + } + } + + private static boolean isInvalid(long address) { + return (address < 0xFF00000000L || address > 0xF000000000000L); + } + + private static void print(long address, int offset, boolean isObject) { + String result = API.readMemoryString(address); + + if (!result.trim().isEmpty() && !result.trim().equals("ERROR")) + System.out.println(formatString(replacements.getOrDefault(result, result), offset, isObject)); + } + + private static String formatString(String s, int offset, boolean isObject) { + return (isObject ? "\u001B[34mOBJ" : "\u001B[32mSTR") + "[" + offset + "]\u001B[0m " + s; + } + + private static void updateReplacements() { + if (REPLACEMENTS_FILE == null || lastModified == REPLACEMENTS_FILE.lastModified()) return; + lastModified = REPLACEMENTS_FILE.lastModified(); + + try (BufferedReader br = new BufferedReader(new FileReader(REPLACEMENTS_FILE))) { + replacements = br.lines() + .map(s -> s.trim().split("=")) + .filter(s -> s.length == 2) + .collect(Collectors.toMap(s -> s[0], s -> s[1])); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + private static File getReplacementsFile() { + File file = new File(FILE_NAME); + + if (file.exists()) return file; + return null; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java new file mode 100644 index 00000000..4e76dc7a --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/debug/SWFUtils.java @@ -0,0 +1,28 @@ +package com.github.manolo8.darkbot.utils.debug; + +import java.io.FileOutputStream; +import java.io.IOException; + +import static com.github.manolo8.darkbot.Main.API; + +public class SWFUtils { + + public static void dumpMainSWF() { + byte[] header = {'F', 'W', 'S', 21}; + long[] addresses = API.queryMemory(header, 10); + + for (long addr : addresses) { + int size = API.readMemoryInt(addr + 4); + if (size < 11_500_000 || size > 13_000_000) continue; + + try (FileOutputStream writer = new FileOutputStream("main.swf")) { + writer.write(API.readMemory(addr, size)); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + System.out.println("SWF not found, are you running the flash client?"); + } + +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/http/Http.java b/src/main/java/com/github/manolo8/darkbot/utils/http/Http.java new file mode 100644 index 00000000..7d1fc98e --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/http/Http.java @@ -0,0 +1,284 @@ +package com.github.manolo8.darkbot.utils.http; + +import com.github.manolo8.darkbot.utils.IOUtils; +import com.github.manolo8.darkbot.utils.ThrowFunction; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Utility for HTTP connections. + * Use it like builder, just one time for instance + */ +public class Http { + private static String DEFAULT_USER_AGENT = "Mozilla/5.0"; + + public static String getDefaultUserAgent() { + return DEFAULT_USER_AGENT; + } + + public static void setDefaultUserAgent(String defaultUserAgent) { + DEFAULT_USER_AGENT = defaultUserAgent; + } + + protected String url; + protected final Method method; + protected final boolean followRedirects; + + //Discord doesn't handle java's user agent... + protected String userAgent = DEFAULT_USER_AGENT; + protected ParamBuilder params; + protected List suppliers; + protected Map headers = new LinkedHashMap<>(); + + protected Http(String url, Method method, boolean followRedirects) { + this.url = url; + this.method = method; + this.followRedirects = followRedirects; + } + + /** + * Creates new instance of Http with provided url. + * Request method is {@link Method#GET} and follows redirects by default. + * + * @param url to connect + * @return new Http + */ + public static Http create(String url) { + return new Http(url, Method.GET, true); + } + + /** + * Creates new instance of Http with provided url and request method. + * Follows redirects by default. + * + * @param url to connect + * @param method of request + * @return new Http + */ + public static Http create(String url, Method method) { + return new Http(url, method, true); + } + + /** + * Creates new instance of Http with provided url and follow redirects. + * Request method is {@link Method#GET} by default. + * + * @param url to connect + * @param followRedirects should follow redirects, response code 3xx + * @return new Http + */ + public static Http create(String url, boolean followRedirects) { + return new Http(url, Method.GET, followRedirects); + } + + /** + * Creates new instance of Http with provided arguments. + * + * @param url to connect + * @param method of request + * @param followRedirects should follow redirects, response code 3xx + * @return new Http + */ + public static Http create(String url, Method method, boolean followRedirects) { + return new Http(url, method, followRedirects); + } + + /** + * Adds action which will be executed at the end of the connection + * + * @param action to execute + * @return current instance of Http + */ + public Http addSupplier(Runnable action) { + if (suppliers == null) suppliers = new ArrayList<>(); + this.suppliers.add(action); + return this; + } + + /** + * Sets or overrides connection header. + * Encoded via {@link java.net.URLEncoder#encode(String, String)} in UTF-8 + * To set header without encoding look {@link Http#setRawHeader(String, String)} + *

+ * Equivalent to {@link HttpURLConnection#setRequestProperty(String, String)} + * + * @param key of header + * @param value of header + * @return current instance of Http + */ + public Http setHeader(String key, String value) { + this.headers.put(ParamBuilder.encode(key), ParamBuilder.encode(value)); + return this; + } + + /** + * Sets or overrides connection header without encoding. + * Equivalent to {@link HttpURLConnection#setRequestProperty(String, String)} + * + * @param key of header + * @param value of header + * @return current instance of Http + */ + public Http setRawHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + /** + * Sets or overrides parameter for POST as body or for GET as + * additional query url only if current url doesn't contains '?' char. + * Is encoded via {@link java.net.URLEncoder#encode(String, String)} + * + * @param key of parameter + * @param value of parameter + * @return current instance of Http + */ + public Http setParam(Object key, Object value) { + if (this.params == null) + this.params = ParamBuilder.create(ParamBuilder.encode(key), ParamBuilder.encode(value)); + else this.params.set(key, value); + return this; + } + + /** + * Sets or overrides parameter for POST as body or for GET as + * additional query url only if current url doesn't contains '?' char. + * Be aware, this wont be encoded via {@link java.net.URLEncoder#encode(String, String)} + * + * @param key of parameter + * @param value of parameter + * @return current instance of Http + */ + public Http setRawParam(Object key, Object value) { + if (this.params == null) + this.params = ParamBuilder.create(key, value); + else this.params.setRaw(key, value); + return this; + } + + /** + * Sets user agent used in connection. + * Default is "Mozilla/5.0". + * + * @param userAgent to use. + * @return current instance of Http + */ + public Http setUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + + /** + * Connects, gets and converts InputStream to String then closes stream. + * Creates new connection on each call + * + * @return body of request as String + * @throws IOException of {@link IOUtils#read(InputStream)} + */ + public String getContent() throws IOException { + return IOUtils.read(getInputStream(), true); + } + + /** + * Gets and closes InputStream of current connection. + * Creates new connection on each call + * + * @throws IOException of {@link Http#getInputStream()} + */ + public void closeInputStream() throws IOException { + getInputStream().close(); + } + + /** + * Gets InputStream of current connection. + * Creates new connection on each call + * + * @return InputStream of connection + * @throws IOException of {@link Http#getConnection()} + */ + public InputStream getInputStream() throws IOException { + return getConnection().getInputStream(); + } + + /** + * Gets InputStream of current connection and applies consumer then closes InputStream. + * Creates new connection on each call + * + *

{@code
+     *         try {
+     *             String result = Http.create("https://example.com")
+     *                     .consumeInputStream(IOUtils::read);
+     *         } catch (IOException e) {
+     *             System.out.println("Something went wrong");
+     *         }
+     * }
+ * + * @param function function which will consume InputStream + * @param type of return + * @return of your expression or null on exception. + */ + @SuppressWarnings("unchecked") + public R consumeInputStream(ThrowFunction function) throws X { + try (InputStream is = getInputStream()) { + return function.apply(is); + } catch (IOException e) { + throw (X) e; + } + } + + /** + * Gets {@link HttpURLConnection} with provided params, + * request method, and body. + * Creates new connection on each call + * + * @return HttpURLConnection + * @throws IOException of connection + */ + public HttpURLConnection getConnection() throws IOException { + return getConnection(null); + } + + /** + * Gets {@link HttpURLConnection} with provided params, + * request method, and body. + * Creates new connection on each call + * + * @param customSettings custom settings of connection which they + * will be consumed after initializing http. + * @return HttpURLConnection + * @throws IOException of connection + */ + public HttpURLConnection getConnection(Consumer customSettings) throws IOException { + if (method == Method.GET && params != null && !url.contains("?")) + url += "?" + params.toString(); + + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + if (customSettings != null) customSettings.accept(conn); + + conn.setInstanceFollowRedirects(followRedirects); + conn.setRequestProperty("User-Agent", userAgent); + if (!headers.isEmpty()) headers.forEach(conn::setRequestProperty); + + if (method == Method.POST && params != null) { + byte[] data = params.getBytes(); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Length", String.valueOf(data.length)); + + try (OutputStream os = conn.getOutputStream()) { + os.write(data); + } + } + if (suppliers != null) suppliers.forEach(Runnable::run); + + return conn; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/utils/http/Method.java b/src/main/java/com/github/manolo8/darkbot/utils/http/Method.java new file mode 100644 index 00000000..f58a2d67 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/http/Method.java @@ -0,0 +1,16 @@ +package com.github.manolo8.darkbot.utils.http; + +/** + * Supported connection methods by Http util. + */ +public enum Method { + /** + * GET connection method. + */ + GET, + + /** + * POST connection method. + */ + POST +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/http/ParamBuilder.java b/src/main/java/com/github/manolo8/darkbot/utils/http/ParamBuilder.java new file mode 100644 index 00000000..7b479fc3 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/http/ParamBuilder.java @@ -0,0 +1,157 @@ +package com.github.manolo8.darkbot.utils.http; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * ParamBuilder is used to make POST bodies, queries for GET request etc. + */ +public class ParamBuilder { + protected Map params = new LinkedHashMap<>(); + + protected ParamBuilder() { } + protected ParamBuilder(Object key, Object value) { this.params.put(key, value); } + protected ParamBuilder(Map params) { this.params.putAll(params); } + + /** + * Creates new instance of ParamBuilder. + * + * @return new ParamBuilder + */ + public static ParamBuilder create() { + return new ParamBuilder(); + } + + /** + * Creates new instance of ParamBuilder with parameter + * Be aware, this parameter wont be encoded via {@link URLEncoder} + * + * @param key of parameter. + * @param value of parameter. + * @return new ParamBuilder + */ + public static ParamBuilder create(Object key, Object value) { + return new ParamBuilder(key, value); + } + + /** + * Creates new instance of ParamBuilder with values of params arg. + * Be aware, this parameter wont be encoded via {@link URLEncoder} + * + * @param params map of parameters. + * @return new ParamBuilder + */ + public static ParamBuilder create(Map params) { + return new ParamBuilder(params); + } + + /** + * Set or overwrite a parameter. + * Will be encoded via URLEncoder in UTF-8 + * + * @param key of parameter. + * @param value of parameter. + * @return current instance of ParamBuilder + */ + public ParamBuilder set(Object key, Object value) { + this.params.put(encode(key), encode(value)); + return this; + } + + /** + * Set or overwrite parameters. + * Will be encoded via URLEncoder in UTF-8 + * + * @param params map of parameters. + * @return current instance of ParamBuilder + */ + public ParamBuilder set(Map params) { + params.forEach(this::set); + return this; + } + + /** + * Set or overwrite a parameter without URLEncoder. + * + * @param key of parameter. + * @param value of parameter. + * @return current instance of ParamBuilder + */ + public ParamBuilder setRaw(Object key, Object value) { + this.params.put(key, value); + return this; + } + + /** + * Set or overwrite parameters without URLEncoder. + * + * @param params map of parameters. + * @return current instance of ParamBuilder + */ + public ParamBuilder setRaw(Map params) { + this.params.putAll(params); + return this; + } + + /** + * Creates bytes from current params in UTF_8 encoding. + * + * @return byte array + */ + public byte[] getBytes() { + return toString().getBytes(StandardCharsets.UTF_8); + } + + /** + * Creates a String of parameters from objects. + * Example: "key=value&anotherKey=anotherValue". + * + * @return String of current parameters + */ + public String toString() { + return this.params.entrySet().stream() + .map(Object::toString) + .collect(Collectors.joining("&")); + } + + /** + * Get current HashMap of parameters. + * + * @return map with current parameters. + */ + public Map getParams() { + return this.params; + } + + /** + * Checks if Objects is instance of String + * and encodes it via {@link URLEncoder#encode(String, String)} in UTF-8 + * else returns raw value. + * + * @param value Object to encode + * @return encoded String or raw Object if is not a String / on exception. + */ + public static Object encode(Object value) { + if (!(value instanceof String)) return value; + return encode((String) value); + } + + /** + * Encodes String via {@link URLEncoder#encode(String, String)} in UTF-8 + * + * @param value to encode + * @return encoded String or raw value on exception + */ + public static String encode(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return value; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/utils/login/Credentials.java b/src/main/java/com/github/manolo8/darkbot/utils/login/Credentials.java new file mode 100644 index 00000000..e9973f9b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/login/Credentials.java @@ -0,0 +1,69 @@ +package com.github.manolo8.darkbot.utils.login; + +import com.github.manolo8.darkbot.config.utils.ByteArrayToBase64TypeAdapter; +import com.github.manolo8.darkbot.utils.Encryption; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Stores user credentials encrypted on disk, using a password+salt + */ +public class Credentials { + private static transient final Type USER_LIST = new TypeToken>(){}.getType(); + public static transient final Gson GSON = new GsonBuilder() + .disableHtmlEscaping() + .registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()) + .create(); + + private byte[] salt; + private String data; // Encrypted data + private transient List users = new ArrayList<>(); // Non-encrypted data, not stored + + public static Credentials create() { + Credentials c = new Credentials(); + c.salt = new byte[32]; + new Random().nextBytes(c.salt); + return c; + } + + public boolean isEmpty() { + return data == null; + } + + public List getUsers() { + return users; + } + + public void decrypt(char[] password) throws GeneralSecurityException { + users = data == null ? new ArrayList<>() : + GSON.fromJson(Encryption.decrypt(data, Encryption.createSecretKey(password, salt)), USER_LIST); + } + + public void encrypt(char[] password) throws GeneralSecurityException { + data = users.isEmpty() ? null : + Encryption.encrypt(GSON.toJson(users), Encryption.createSecretKey(password, salt)); + } + + public static class User { + public String u, p; + + public User() {} + + public User(String username, String password) { + this.u = username; + this.p = password; + } + + @Override + public String toString() { + return u; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/utils/login/LoginData.java b/src/main/java/com/github/manolo8/darkbot/utils/login/LoginData.java new file mode 100644 index 00000000..b1587d3b --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/login/LoginData.java @@ -0,0 +1,54 @@ +package com.github.manolo8.darkbot.utils.login; + +public class LoginData { + private String username, password, sid, url, preloaderUrl, params; + + public void setCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + public void setSid(String sid, String url) { + this.sid = sid; + this.url = url; + } + + public void setPreloader(String preloaderUrl, String params) { + this.preloaderUrl = preloaderUrl; + this.params = params; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getSid() { + return sid; + } + + public String getUrl() { + return url; + } + + public String getPreloaderUrl() { + return preloaderUrl; + } + + public String getParams() { + return params; + } + + @Override + public String toString() { + return "LoginData{" + + "sid='" + sid + '\'' + + ", url='" + url + '\'' + + ", preloaderUrl='" + preloaderUrl + '\'' + + ", params='" + params + '\'' + + '}'; + } +} diff --git a/src/main/java/com/github/manolo8/darkbot/utils/login/LoginUtils.java b/src/main/java/com/github/manolo8/darkbot/utils/login/LoginUtils.java new file mode 100644 index 00000000..c6b26bd0 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/utils/login/LoginUtils.java @@ -0,0 +1,150 @@ +package com.github.manolo8.darkbot.utils.login; + +import com.github.manolo8.darkbot.config.ConfigEntity; +import com.github.manolo8.darkbot.gui.login.LoginForm; +import com.github.manolo8.darkbot.gui.utils.Popups; +import com.github.manolo8.darkbot.utils.I18n; +import com.github.manolo8.darkbot.utils.http.Http; +import com.github.manolo8.darkbot.utils.http.Method; + +import javax.swing.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.HttpCookie; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LoginUtils { + private static final Pattern LOGIN_PATTERN = Pattern.compile("\"bgcdw_login_form\" action=\"(.*)\""); + private static final Pattern DATA_PATTERN = Pattern.compile("\"src\": \"([^\"]*)\".*}, \\{(.*)}"); + + private static final Map FORCED_PARAMS = new HashMap<>(); + static { + String lang = I18n.getLocale().getLanguage(); + if (!lang.isEmpty() && ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.FORCE_GAME_LANGUAGE) + FORCED_PARAMS.put("lang", lang); + FORCED_PARAMS.put("display2d", "2"); + FORCED_PARAMS.put("autoStartEnabled", "1"); + } + + public static LoginData performUserLogin() { + LoginForm panel = new LoginForm(); + + JOptionPane pane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}, null); + pane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); + + Popups.showMessageSync("Login", pane, panel::setDialog); + + LoginData loginData = panel.getResult(); + if (loginData.getPreloaderUrl() == null || loginData.getParams() == null) System.exit(0); + return loginData; + } + + public static void usernameLogin(LoginData loginData) { + String loginUrl = Http.create("https://www.darkorbit.com/") + .consumeInputStream(LoginUtils::getLoginUrl); + + CookieManager cookieManager = new CookieManager(); + CookieHandler.setDefault(cookieManager); + + try { + Http.create(loginUrl, Method.POST) + .setParam("username", loginData.getUsername()) + .setParam("password", loginData.getPassword()) + .closeInputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + + CookieHandler.setDefault(null); + + HttpCookie cookie = cookieManager.getCookieStore().getCookies().stream() + .filter(c -> c.getName().equalsIgnoreCase("dosid")) + .filter(c -> c.getDomain().matches(".*\\d+.*")) + .findFirst().orElseThrow(WrongCredentialsException::new); + + loginData.setSid(cookie.getValue(), cookie.getDomain()); + } + + public static void findPreloader(LoginData loginData) { + Http req = Http.create("https://" + loginData.getUrl() + "/indexInternal.es?action=internalMapRevolution", false) + .setRawHeader("Cookie", "dosid=" + loginData.getSid()); + + if (ConfigEntity.INSTANCE.getConfig().BOT_SETTINGS.SPOOF_CLIENT) + req.setUserAgent("BigpointClient/1.1.0"); + + String flashEmbed = req.consumeInputStream(inputStream -> + new BufferedReader(new InputStreamReader(inputStream)) + .lines() + .filter(l -> l.contains("flashembed(")) + .findFirst() + .orElseThrow(WrongCredentialsException::new)); + + Matcher m = DATA_PATTERN.matcher(flashEmbed); + if (m.find()) loginData.setPreloader(m.group(1), replaceParameters(m.group(2))); + else throw new WrongCredentialsException("Can't parse flashembed vars"); + } + + private static String replaceParameters(String params) { + params = params.replaceAll("\"", "").replaceAll(",", "&").replaceAll(": ", "="); + for (Map.Entry replaces : FORCED_PARAMS.entrySet()) { + params = params.replaceAll(replaces.getKey() + "=[^&]+", replaces.getKey() + "=" + replaces.getValue()); + } + return params; + } + + private static String getLoginUrl(InputStream in) { + return new BufferedReader(new InputStreamReader(in)).lines() + .map(LOGIN_PATTERN::matcher) + .filter(Matcher::find) + .map(matcher -> matcher.group(1).replace("&", "&")) + .findFirst().orElseThrow(WrongCredentialsException::new); + } + + public static Credentials loadCredentials() { + Path file = Paths.get("credentials.json"); + if (!Files.exists(file)) return Credentials.create(); + + try (BufferedReader reader = Files.newBufferedReader(Paths.get("credentials.json"))) { + return Credentials.GSON.fromJson(reader, Credentials.class); + } catch (Exception e) { + e.printStackTrace(); + } + return Credentials.create(); + } + + public static void saveCredentials(Credentials c, char[] passwd) throws GeneralSecurityException { + Path file = Paths.get("credentials.json"); + + c.encrypt(passwd); + + try (BufferedWriter writer = Files.newBufferedWriter(file)) { + Credentials.GSON.toJson(c, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static class WrongCredentialsException extends IllegalArgumentException { + + public WrongCredentialsException() { + this("Wrong login data"); + } + + public WrongCredentialsException(String s) { + super(s); + } + } + +} diff --git a/src/main/java/eu/darkbot/api/DarkBoat.java b/src/main/java/eu/darkbot/api/DarkBoat.java new file mode 100644 index 00000000..c38788a7 --- /dev/null +++ b/src/main/java/eu/darkbot/api/DarkBoat.java @@ -0,0 +1,49 @@ +package eu.darkbot.api; + +public class DarkBoat { + + static { + System.loadLibrary("lib/DarkBoatAPI"); + } + + public native void setData(String url, String sid, String preloader, String vars); + public native void createWindow(); + public native void setSize(int width, int height); + public native void setVisible(boolean visible); + // When enabled browser is further hidden, lowering CPU usage, but doesn't work on all systems + public native void setMinimized(boolean visible); + public native void reload(); + public native boolean isValid(); + public native long getMemoryUsage(); + public native int getVersion(); + + public native void keyClick (int keyCode); + public native void sendText (String text); + + public native void mouseMove (int x, int y); + public native void mouseDown (int x, int y); + public native void mouseUp (int x, int y); + public native void mouseClick(int x, int y); + + public native int readInt (long address); + public native long readLong (long address); + public native double readDouble (long address); + public native boolean readBoolean(long address); + public native String readString (long address); // bugged encoding + public native byte[] readBytes (long address, int length); + + public native void replaceInt (long address, int oldValue, int newValue); + public native void replaceLong (long address, long oldValue, long newValue); + public native void replaceDouble (long address, double oldValue, double newValue); + public native void replaceBoolean(long address, boolean oldValue, boolean newValue); + + public native void writeInt (long address, int value); + public native void writeLong (long address, long value); + public native void writeDouble (long address, double value); + public native void writeBoolean (long address, boolean value); + public native void writeBytes (long address, byte... bytes); + + public native long[] queryInt (int value , int maxSize); + public native long[] queryLong (long value , int maxSize); + public native long[] queryBytes (byte[] pattern, int maxSize); +} \ No newline at end of file diff --git a/src/main/java/eu/darkbot/api/NativeApi.java b/src/main/java/eu/darkbot/api/NativeApi.java new file mode 100644 index 00000000..c556b8da --- /dev/null +++ b/src/main/java/eu/darkbot/api/NativeApi.java @@ -0,0 +1,40 @@ +package eu.darkbot.api; + +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; + +public class NativeApi { + static { + System.loadLibrary("lib/core-api"); + } + + public native boolean createBot(int botId); + public native boolean isValid(int botId); + public native boolean removeBot(int botId); + public native boolean sendMessage(int botId, String message); + public boolean sendMessage(int botId, Object... params) { + String command = Arrays.stream(params).map(Objects::toString).collect(Collectors.joining("|")) + "#"; + return sendMessage(botId, command); + } + + public native void mouseClick(int botId, int delay, int x, int y); + + public native long[] queryMemoryInt (int botId, int value, int maxQuantity); + public native long[] queryMemoryLong(int botId, long value, int maxQuantity); + public native long[] queryMemory (int botId, byte[] query, int maxQuantity); + + public native byte[] readMemory (int botId, long address, int length); + public native int readMemoryInt (int botId, long address); + public native long readMemoryLong (int botId, long address); + public native float readMemoryFloat (int botId, long address); + public native double readMemoryDouble (int botId, long address); + public native boolean readMemoryBoolean(int botId, long address); + + public native boolean writeMemoryInt (int botId, long address, int value); + public native boolean writeMemoryLong (int botId, long address, long value); + public native boolean writeMemoryFloat (int botId, long address, float value); + public native boolean writeMemoryDouble (int botId, long address, double value); + public native boolean writeMemoryBoolean(int botId, long address, boolean value); + +} diff --git a/src/main/resources/add.png b/src/main/resources/add.png new file mode 100644 index 00000000..c16798ea Binary files /dev/null and b/src/main/resources/add.png differ diff --git a/src/main/resources/close.png b/src/main/resources/close.png new file mode 100644 index 00000000..24b2d409 Binary files /dev/null and b/src/main/resources/close.png differ diff --git a/src/main/resources/config.png b/src/main/resources/config.png new file mode 100644 index 00000000..deb81357 Binary files /dev/null and b/src/main/resources/config.png differ diff --git a/src/main/resources/config_unloaded.png b/src/main/resources/config_unloaded.png new file mode 100644 index 00000000..2b0ae4a4 Binary files /dev/null and b/src/main/resources/config_unloaded.png differ diff --git a/src/main/resources/discord.png b/src/main/resources/discord.png new file mode 100644 index 00000000..d0d08b42 Binary files /dev/null and b/src/main/resources/discord.png differ diff --git a/src/main/resources/edit.png b/src/main/resources/edit.png new file mode 100644 index 00000000..dae769bc Binary files /dev/null and b/src/main/resources/edit.png differ diff --git a/src/main/resources/hamburger.png b/src/main/resources/hamburger.png new file mode 100644 index 00000000..b119cd81 Binary files /dev/null and b/src/main/resources/hamburger.png differ diff --git a/src/main/resources/lang/strings_bg.properties b/src/main/resources/lang/strings_bg.properties new file mode 100644 index 00000000..ecff5507 --- /dev/null +++ b/src/main/resources/lang/strings_bg.properties @@ -0,0 +1,115 @@ +config.general=Общо +config.general.current_module=Модул +config.general.working_map=Действаща карта +config.general.offensive=Атакуваща конфигурация +config.general.offensive.desc=Използва се за унищожаване на NPC +config.general.roam=Опознавателна конфигурация +config.general.roam.desc=Използва се за разкриване на картата, търсене на посоченото +config.general.run=Бягаща Конфигурация +config.general.run.desc=Използва се за измъкване до Зона за ненападение или близък портал за ск. +config.general.formation_check=Честота на проверка на дрон формация +config.general.formation_check.desc=Проверява формацията на всеки (х)ms секунди, при нула няма проверка +config.general.safety=Настройки безопасност +config.general.safety.repair_hp_range=поддържай hp между х% и з% +config.general.safety.repair_hp_range.desc=Започни поправка при % под х зададения. +config.general.safety.repair_hp_no_npc=минимум hp% за обикаляне или опознаване на картата +config.general.safety.repair_hp_no_npc.desc=мин. количество точки живот преди стоп за ремонт +config.general.safety.repair_to_shield=Мин. количество щит % за възтановяване +config.general.safety.repair_to_shield.desc=Мин. количество щитов % за връщане към работа. +config.general.safety.repair=Възтановяваща Конфигурация +config.general.safety.repair.desc=Оказва коя конф. да поправя след като е използвана бягаща маневра +config.general.safety.max_deaths=Максимален брой на унищожения +config.general.safety.max_deaths.desc=Максимален брой на разрушения, при достигнат брой (х), ботът спира работа. +config.general.safety.revive_location=Точка за поправка +config.general.safety.revive_location.desc=Посочете място където да бъде върнат на картата корабът ви. +config.general.safety.wait_before_revive=Време преди връщане на картата при унищожение +config.general.safety.wait_before_revive.desc=Време след което ботът да възтанови кораба след унищ. +config.general.safety.wait_after_revive=Престой на портал (в з-на за ненап.) след възт. на унищ. кораб +config.general.safety.wait_after_revive.des=Време за престой в защитената от нападения зона с цел пълна поправка. +config.general.running=Настройки за избягващи маневри +config.general.running.run_from_enemies=Бягане от вражески играчи +config.general.running.run_from_enemies.desc=При контакт с вражески играч какво действие да предприеме бота +config.general.running.remember_enemies_for=Запаметяване вражески кораби за (х)време. +config.general.running.remember_enemies_for.desc=Започва избягващи маневри при навлизане на вражески играч, нападнал ви наскоро. х(време което да бягате от един и същ враг който ви е унищ.) +config.general.running.run_from_enemies_sight=Започва избягващи маневри при зрителен контакт с враг +config.general.running.run_from_enemies_sight.desc=Започва избягващи маневри при локализиране на миникартата. +config.general.running.stop_running_no_sight=Прекратява избягващи маневри след изчезване на вражески играч от миникартата +config.general.running.stop_running_no_sight.desc=При загуба на визуален контакт с вражески играч, връщане към работа +config.general.running.max_sight_distance=Дистанция за визуален контакт. +config.general.running.max_sight_distance.desc=Спира избягващи маневри при дистанция по-голяма от зададената. +config.general.running.ship_ability=Корабно умение +config.general.running.ship_ability.desc=Функция на кораба или дизайна при извършване на избягващи маневри. +config.general.running.ship_ability_min=Мин. разстояние за ползване на специална функция(способност) +config.general.running.ship_ability_min.desc=Мин. разст. за активиране на спец. функция (разст. до зона за ненап.) +config.general.running.run_furthest_port=Разстояние за работа от зоната на портала +config.general.running.run_furthest_port.desc=Зададената стойност оказва на бота, кога да тръгне към портала за хиперскок и кога да тръгне в избягващи маневри при контакт с вражески играч. +config.general.roaming=Целенасочено движение и Предпочитани зони за работа +config.general.roaming.keep=Достигане на целевата позиция дори с промяна на маршрута. +config.general.roaming.sequential=Обиколка на целевите зони в последователна схема. +config.general.roaming.only_kill_preferred=Унищожаване на НЛО само в предпочитани зони за работа. +config.general.roaming.only_kill_preferred.desc=Стартира нападение над враг само и единствено ако се намира в предпочитани зони за работа. + +config.collect=Събиране на материали +config.collect.stay_away_from_enemies=Избягвайте контакт с врагове +config.collect.auto_cloack=Автоматичен камуфлаж при събираща функция на работа +config.collect.auto_cloack_key=Клавиш за камуфлажно CPU +config.collect.radius=Радиус за събиране на ресурси при обстрел +config.collect.radius.desc=Дистанция за събиране на ресурси докато се намира под обстрел или отстрелва враг. + +config.loot=Функция НЛО унищожител +config.loot.sab=САБ Щитоабсорбираща муниция +config.loot.sab.desc=Автоматично превключване от муниции за щети към щито-поглъщащи такива в битка +config.loot.sab.enabled=Самостоятелно превключване при нужда. +config.loot.sab.key=Клавиш на лентата за превключване САБ +config.loot.sab.percent=Превключи щитово абсорбиране при % под (х)% +config.loot.sab.npc_amount=Абсорбиране на вражески щит до минимум (стойността щета от саб муницията ви) +config.loot.ammo_key=Клавиш за превключване на муниции за стрелба +config.loot.ship_ability=Спец. корабно умение за нападение(веном, диминишър и киборг) +config.loot.ship_ability_min=Минимална стойност точки живот на опонент за включване на умение +config.loot.ship_ability_min.desc=Минимална стойност на точките на опонента в която да се използва специалното умение в битката +config.loot.max_circle_iterations=Ходове аванс при кръговидно движение при нападение +config.loot.max_circle_iterations.desc=Пресмятане на брой движения, за промяна посоката на въртене при нападение. 0 -деактивиране , 3-5 препоръчителна стойност +config.loot.run_config_in_circle=Въртене при нападение и конфигурация за преследване +config.loot.run_config_in_circle.desc=Превключване на бърза конфигурация за преследване на вражески обекти на малко точки живот. +config.loot.group_npcs=Подредба на извънземните според действието +config.loot.group_npcs.desc=Последователна подредба на вражески НЛО според галактическата карта или портал +config.loot.npc_distance_ignore=Прекрати обстрел на вражески НЛО при разстояние повече от (1,500-3,500 оптимално) + +config.pet=Pet +config.pet.enabled=Включи РЕТ +config.pet.enabled.desc=При отсъствие от бойната карта игнорира всички редове под този +config.pet.module=Модули за работа +config.pet.module.desc=0 - Пасивен режим
1 - Защитен Режим
2 - Налични модули различни от базови + +config.miscellaneous=Работни настройки +config.miscellaneous.refresh_time=Време за рестарт на програмата +config.miscellaneous.refresh_time.desc=Интервал за самостоятелно рестартиране на бот конфигурацията +config.miscellaneous.pause_for=Почивка след рестартиране +config.miscellaneous.pause_for.desc=Интервал за почивка след извършено рестартиране на конфигурацията +config.miscellaneous.drone_repair_percentage=незадължително поради липсващи щети в-у дроните +config.miscellaneous.drone_repair_percentage.desc=поправя дроните при щети същите или по-малки от зададените в %. + +config.bot_settings=Конфигуриране на бот система +config.bot_settings.locale=Език за интерфейса на бота +config.bot_settings.zone_resolution=Разделително число при маркиране на зони за работа/отбягване. +config.bot_settings.zone_resolution.desc=стойност <30 непрепоръчително поради непрецизност на разделителните полета. 60 =/< Прецизност на отбелязване в прозореца за избор на зони. +config.bot_settings.map_start_stop=Старт/стоп при клик на картата на програмата. +config.bot_settings.map_start_stop.desc=Щракнете с ляв бутон за старт/стоп на работа. Щракнете с десния бутон за да укажете посоката за движение на кораба. +config.bot_settings.confirm_exit=Потвърди преди излизане от бот конфигурацията +config.bot_settings.confirm_exit.desc=Сигурни ли сте, че искате да напуснете конфигурацията, против инцидентно затваряне на прозореца. +config.bot_settings.min_tick=Минимално време за изпълнение на задание +config.bot_settings.dev_stuff=Покажи стойности за програмисти +config.bot_settings.dev_stuff.desc=Активирането на тази функция е непрепоръчителна за любители, поради високия разход на процесорно време на работа. +config.bot_settings.display=Дисплей +config.bot_settings.display.show_names=Показване на потребителски имена на картата +config.bot_settings.display.show_names.desc=Изписва наименованията на играчите, вместо класическия начин при миникартата с точки. +config.bot_settings.display.hide_name=Скриване на наименование в прозореца на звездната карта +config.bot_settings.display.hide_name.desc=Прикрива наименованието на пилота в прозореца на звездната карта +config.bot_settings.display.trail_length=Дължина на опашка изминато разстояние от кораба в интерфейса на конфигурацията ви +config.bot_settings.display.trail_length.desc=Време, през което пътеката трябва да бъде в картата, в секунди +config.bot_settings.display.show_zones=Показване на предпочитани/отбягвани зони върху интерфейса на конфигурацията +config.bot_settings.display.show_zones.desc=Показване на отбягвани и предпочитани зони на за работа в-у картата на бот конфигурацията ви +config.bot_settings.display.button_size=Големина на визуализиране на Графичния Потребителския Интерфейс(graphical user interface a.k.a. GUI) +config.bot_settings.display.button_size.desc=Промяна на конфигурацията на интерфейса и промяна размера на главния работен прозорец. + +translation.credit=@Strange Guy#2533 & @Valöbs#1786 \ No newline at end of file diff --git a/src/main/resources/lang/strings_cs.properties b/src/main/resources/lang/strings_cs.properties new file mode 100644 index 00000000..91bb9155 --- /dev/null +++ b/src/main/resources/lang/strings_cs.properties @@ -0,0 +1,337 @@ +# Config tabs +tabs.general=Hlavní nastavení +tabs.general.desc=Hlavní nastavení bota +tabs.zones=Zony mapy +tabs.zones.desc=Výběr preferovaných, zakázaných a bezpečných míst +tabs.preferred_zones=Preferované zóny +tabs.preferred_zones.desc=Bot se bude pohybovat v následujících zónách. +tabs.avoided_zones=Zakázané zóny +tabs.avoided_zones.desc=Bot nebude lítat do následujících zón. +tabs.safety_places=Bezpečná místa +tabs.safety_places.desc=Nastavení bezpečných míst. +tabs.players=Hráči +tabs.players.desc=Záložka s hráči +tabs.plugins=Pluginy +tabs.plugins.desc=Info a konfigurace pluginů + +# Config tree +config.general=Hlavní nastavení +config.general.current_module=Modul +config.general.working_map=Botovací mapa +config.general.offensive=Útočná konfigurace +config.general.offensive.desc=K zabíjení NPC. +config.general.roam=Roamovací konfigurace +config.general.roam.desc=K hledání boxů/NPC. +config.general.run=Utíkací konfigurace +config.general.run.desc=K utíkání a změně mapy. +config.general.formation_check=Kontrola formace +config.general.formation_check.desc=Kontrola formace každých X sekund, 0 pro vypnutí. +config.general.safety=Bezpečí +config.general.safety.repair_hp_range=Udržovat HP mezi +config.general.safety.repair_hp_range.desc=Uteče pokud je hodnota HP menší než X% (Pouze střelba NPC) +config.general.safety.repair_hp_no_npc=Minimální HP při roamingu. +config.general.safety.repair_hp_no_npc.desc=Minimální HP při hledání NPC/Boxů. +config.general.safety.repair_to_shield=Minimální hodnota štítů +config.general.safety.repair_to_shield.desc=Oprava štítů nad hodnotu X%. +config.general.safety.repair=Opravovací konfigurace +config.general.safety.repair.desc=Používá se k opravování. +config.general.safety.max_deaths=Maximum smrtí +config.general.safety.max_deaths.desc=Maximální počet smrtí, při dosažení se bot zastaví. +config.general.safety.revive_location=Místo opravy +config.general.safety.revive_location.desc=Kde se bot bude opravovat. +config.general.safety.revive_location.list.base=Základna +config.general.safety.revive_location.list.portal=Portál +config.general.safety.revive_location.list.spot=Na místě +config.general.safety.wait_before_revive=Počkat před opravou (sekundy). +config.general.safety.wait_before_revive.desc=Počet sekund než se bot opraví. +config.general.safety.wait_after_revive=Počkat po opravení (sekundy). +config.general.safety.wait_after_revive.des=Počet sekund po opravení. +config.general.running=Utíkání +config.general.running.run_from_enemies=Utíkaní při napadení +config.general.running.run_from_enemies.desc=Začne utíkat pouze pokud nepřítel vystřelí do lodě. +config.general.running.remember_enemies_for=Zapamatovat si nepřítele po dobu +config.general.running.remember_enemies_for.desc=Doba po kterou si bot bude pamatovat nepřítele. +config.general.running.run_from_enemies_sight=Utíkat před nepŕítelem v dosahu +config.general.running.run_from_enemies_sight.desc=Uteče pokud uvidí nepŕítele na minimapě. +config.general.running.stop_running_no_sight=Přestat po vzdálení se nepříteli +config.general.running.stop_running_no_sight.desc=Přestene utíkat pokud se bot vzdálí od nepŕítele. +config.general.running.max_sight_distance=Maximální dosah dohledu +config.general.running.max_sight_distance.desc=Přestat utíkat, pokud je nepŕítel dále jak. +config.general.running.ship_ability=Schopnost lodi +config.general.running.ship_ability.desc=Stisknout při napadení nepřítelem +config.general.running.ship_ability_min=Minimální dosah pro schopnost +config.general.running.ship_ability_min.desc=Minimální dosah do bezpečí pro použití schopnosti. +config.general.running.run_furthest_port=Maximální dosah nejbližšího portu +config.general.running.run_furthest_port.desc=Uteče k portu dál od nepřítele, pokud není port dál než.. +config.general.roaming=Roaming & preferované oblasti +config.general.roaming.keep=Dostat se do daného bodu za každou cenu +config.general.roaming.sequential=Roamovat pouze v preferovaných zónách +config.general.roaming.only_kill_preferred=Střílet NPC pouze v preferovaných zonách +config.general.roaming.only_kill_preferred.desc=Vybere a zabije NPC pouze pokud jsou uvnitř preferované zóny. + +config.collect=Sbírání +config.collect.stay_away_from_enemies=Zůstat dále on nepřátel +config.collect.auto_cloack=Automatické maskování +config.collect.auto_cloack_key=Klávesa pro maskování +config.collect.radius=Sbírání při radiu zabíjení +config.collect.radius.desc=Radius sbírání surovin při zabíjení NPC, 0 pro vypnutí. + +config.collect.box_table.resource=Surovina +config.collect.box_table.resource.desc=Boxy a suroviny které bot uvidí. +config.collect.box_table.collect=Sbírat +config.collect.box_table.collect.desc=Sbírat surovinu ? +config.collect.box_table.wait=Čekat (ms) +config.collect.box_table.wait.desc=Čas pro sběr, v milisekundách +config.collect.box_table.priority=Priorita +config.collect.box_table.priority.desc=Boxík s prioritou 1 bude sebrán dříve jako s prioritou 2 + +config.loot=Npc killer +config.loot.sab=Sabování +config.loot.sab.desc=Automatické sabování +config.loot.sab.enabled=Zapnout? +config.loot.sab.key=Klávesa sab +config.loot.sab.percent=Sabovat pod X% štítů +config.loot.sab.npc_amount=Minimální štíty NPC +config.loot.rsb=RSB +config.loot.rsb.desc=Použije RSB na NPC s vlajkou UR +config.loot.rsb.enabled=Povolit? +config.loot.rsb.key=Klávesa +config.loot.rsb.ammo_refresh=Cooldown RSB +config.loot.ammo_key=Základní munice +config.loot.ship_ability=Klávesa útočné schopnosti +config.loot.ship_ability_min=Minimální životy NPC +config.loot.ship_ability_min.desc=Minimální životy NPC pro použití schopnosti. +config.loot.max_circle_iterations=Počet rotací +config.loot.max_circle_iterations.desc=Max počet interací pro změnu směru točení, 0 pro vypnutí. +config.loot.run_config_in_circle=Použít utíkací konfu pokud NPC utíká +config.loot.run_config_in_circle.desc=Použije utíkací konfu pro dohnání NPC +config.loot.group_npcs=Seskupit stejné NPC +config.loot.group_npcs.desc=Seskupí stejné NPC v bránách. +config.loot.npc_distance_ignore=Ignorovat NPC které jsou dále jak + +config.loot.npc_table.name=Název NPC +config.loot.npc_table.name.desc=Názvy NPC, název končící * je seskupený +config.loot.npc_table.radius=Rádius +config.loot.npc_table.radius.desc=Rádius točení NPC +config.loot.npc_table.priority=Priorita +config.loot.npc_table.priority.desc=NPC s prioritou 1 bude stříleno dříve než NPC s prioritou 2 +config.loot.npc_table.kill=Sestřelit? +config.loot.npc_table.kill.desc=Sestřilit toto NPC ? +config.loot.npc_table.attack_key=Munice +config.loot.npc_table.attack_key.desc=Munice pro dané NPC, pokud není nastaveno, bude použita Základní munice +config.loot.npc_table.attack_formation=Formace +config.loot.npc_table.attack_formation.desc=Speciální formace, pokud bude prázdné, bude použita základní formace. +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Speciální nastavení + +config.loot.npc_table.extra.no_circle=Netočit NPC +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=Nebude točit NPC ale zůstane v radiusu +config.loot.npc_table.extra.ignore_ownership=Ignorovat vlastnictví +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Střílet i když NPC zbělá. +config.loot.npc_table.extra.ignore_attacked=Stŕílet střílené +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Střílet i když NPC střílí někdo jiný +config.loot.npc_table.extra.passive=Pasivní +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Bude střílet NPC, pouze, pokud první vystřelí. +config.loot.npc_table.extra.attack_second=Útočit druhý +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Bude střílet pouze, pokud NPC střílí někdo jiný.
Musí být povoleno `Ignorovat vlastnictví(IO)` a `Ignorovat vlastnictví(IA)` +config.loot.npc_table.extra.use_rsb=Použít RSB +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Použije RSB na tohle NPC +config.loot.npc_table.extra.pet_locator=Pet lokalizátor +config.loot.npc_table.extra.pet_locator.short=PL +config.loot.npc_table.extra.pet_locator.desc=Použije lokalizátor peta pro dané NPC + +config.pet=Pet +config.pet.enabled=Používat peta? +config.pet.enabled.desc=Při vypnutí bude bot ignorovat všechny funkce peta. +config.pet.module_id=číslo mechanismu +config.pet.module.desc=Seznam se aktualizuje když zapnete peta + +config.group=Skupina +config.group.accept_invites=Přijmat pozvánky ? +config.group.accept_invites.desc=Bot bude přijmat pozvánky +config.group.whitelist_tag=Přijmat pozvánky od: +config.group.whitelist_tag.desc=Přijmat pozvánky pouze od určité skupiny +config.group.invite_tag=Posílat pozvánky pro: +config.group.invite_tag.desc=Bot bude posílat pozvánky dané skupině +config.group.open_invites=Povolit pozvánky(Velitel) +config.group.open_invites.desc=Povolí pozvánky ve hře +config.group.kick_tag=Vyhodit hráče (Velitel) +config.group.kick_tag.desc=Vyhodí hráče ze skupiny + +config.miscellaneous=Ostatní +config.miscellaneous.refresh_time=Relog každých +config.miscellaneous.refresh_time.desc=Každých X minut dá relog. +config.miscellaneous.pause_for=Pauza po relogu +config.miscellaneous.pause_for.desc=Pozastaví bota na X minut po relogu, 0 pro vypnutí. +config.miscellaneous.drone_repair_percentage=Oprava letounů na +config.miscellaneous.drone_repair_percentage.desc=Oprava letounů na X% procentech + +config.bot_settings=Nastavení bota +config.bot_settings.locale=Jazyk +config.bot_settings.force_game_language=Vynutit jazyk ve hře +config.bot_settings.force_game_language.desc=Bot nastaví jazyk ve hře podle jazyku bota. +config.bot_settings.zone_resolution=Přesnost zóny (rastr) +config.bot_settings.zone_resolution.desc=Velikost mŕížky na výšku a šířku +config.bot_settings.map_start_stop=Spustit/Zastavit kliknutím na mapu +config.bot_settings.map_start_stop.desc=Levé kliknutí pro spuštění/zastavení bota. Pravé pro pohyb lodi. +config.bot_settings.confirm_exit=Potvrzení ukončení +config.bot_settings.confirm_exit.desc=Potvrzení před vypnutím bota. +config.bot_settings.min_tick=Minimální tick time +config.bot_settings.dev_stuff=Zobrazení vývojářských možností +config.bot_settings.dev_stuff.desc=Zapnutím bude bot používat více cpu. +config.bot_settings.display=Zobrazení +config.bot_settings.display.show_names=Zobrazit jména hráčů +config.bot_settings.display.show_names.desc=Zobrazí jména hráčů na mapě bota. +config.bot_settings.display.hide_name=Skrýt jméno +config.bot_settings.display.hide_name.desc=Skryje vaše jméno nad HP. +config.bot_settings.display.hide_group_names=Skrýt jména ve skupině +config.bot_settings.display.trail_length=Délka stopy +config.bot_settings.display.trail_length.desc=Délka zobrazování stopy v sekundách. +config.bot_settings.display.show_zones=Zobrazovat zóny v minimapě +config.bot_settings.display.show_zones.desc=Povolí zobrazení preferovaných/zakázaných zón v minimapě. +config.bot_settings.display.button_size=Velikost tlačítek +config.bot_settings.display.button_size.desc=Změní velikost tlačítek v horní liště. + +# Safety places +safety_places.type.portal=Portál +safety_places.type.cbs=Stanice +safety_places.type.base=Základna + +safety_places.run_mode= [Všechno] Použít pro +safety_places.run_mode.always=Vždy +safety_places.run_mode.enemy_flee_only=Utíkání (Nepŕítel v dohledu) +safety_places.run_mode.repair_only=Oprava (Bez nepřátel) +safety_places.run_mode.refresh=Refresh +safety_places.run_mode.never=Nikdy + +safety_places.jump_mode= [Portál] Proskočit pokud +safety_places.jump_mode.never=Nikdy +safety_places.jump_mode.escaping=Utíkání (Střelba) +safety_places.jump_mode.fleeing=Utíkání (Nepŕítel v dohledu) +safety_places.jump_mode.repairing=Oprava +safety_places.jump_mode.always=Vždy +safety_places.jump_mode.always_other_side=Vždy (Po portnutí) + +safety_places.cbs_mode= [Stanice] Použít pokud je +safety_places.cbs_mode.ally=Spojenecká stanice +safety_places.cbs_mode.ally_neutral=Spojenecká nebo žádná stanice + +#Players +players.add_player=Přidat hráče +players.add_player.by_name=Podle jména +players.add_player.by_id=Podle ID +players.add_player.username=Jméno hráče: +players.add_player.user_id=Id hráče: +players.add_player.invalid_id=Neplatné ID +players.add_player.invalid_id.no_number={0} není platné číslo +players.remove_player.title=Jsi si jistý? +players.remove_player.msg=Opravdu chceš odebrat {0} hráčů? + +players.tag_default.unset=Nenastaveno +players.tag_default.all=Všechny +players.tag_default.none=Nikoho +players.select_players.warn.title=První vyberte hráče +players.select_players.warn.add=Musíš vybrat hráče kterým chcete přidat vlajku +players.select_players.warn.remove=Vyberte hráče kterým chcete odebrat vlajku +players.select_players.warn.remove_player=Vyber hráče kterého chceš odebrat +players.delete_tag.confirm.title=Jste si jistý ? +players.delete_tag.confirm.msg=Opravdu chcete odebrat značku {0}? +# Plugins +plugins.reload=Reload pluginů +plugins.config_button.enabled.desc=Zobrazit nastavení +plugins.config_button.disabled.desc=nastavení je zakázáno, nebo není načtené +plugins.config_button.popup=Nelze upravit nastavení +plugins.config_button.popup.desc=Nastavení není povolené pro nenačtené pluginy \nPovol plugin a načti jej, a zkus to znovu. + +plugins.issues.invalid_json=Neplatný plugin.json +plugins.issues.invalid_json.desc=Minimální verze je {0} ale doporučená verze je {1} +plugins.issues.bot_update=Nutná aktualizace bota +plugins.issues.bot_update.desc=Plugin potřebuje verzi {0}, Z toho důvodu nemůže použít verzi {1} +plugins.issues.loaded_twice=Plugin je již načten. +plugins.issues.loaded_twice.desc=Plugin se stejným jménem je již načten. +plugins.issues.plugin_update=Pokud je to možné, použij novější verzi pluginu. +plugins.issues.plugin_update.desc=Plugin je dělaný na verzi {0}, Takže nemusí fungovat na verzi {1} + +plugins.issues.signature.plugin_not_signed=Plugin nelze načíst +plugins.issues.signature.plugin_not_signed.desc=Tenhle plugin nebyl podepsán a nebo má špatný podpis +plugins.issues.signature.unknown_signature=Neznámý podpis +plugins.issues.signature.unknown_signature.desc=Tenhle plugin byl podepsán nedůvěryhodným certifikátem +plugins.issues.signature.invalid_signature=Invalid signature +plugins.issues.signature.invalid_signature.desc=The plugin has an invalid signature or has been tampered with + +# Main GUI +gui.hamburger_button.home=Hlavní stránka +gui.hamburger_button.reload=Reload +gui.hamburger_button.copy_sid=Okopírovat SID +gui.hamburger_button.discord=Discord +gui.config_button=Zobrazit/Zakrýt nastavení bota +gui.start_button=Zapnout/Pauznout bota +gui.visibility_button=Zobrazit/Zakrýt herní okno +gui.pin_button=Zůstat na popředí + +gui.exit=Opravdu chcete vypnout bota? +gui.exit.desc=Potvrzení vypnutí bota, vypnout můžete v nastavení > nastavení bota +gui.exit.yes_button=Ano +gui.exit.cancel_button=Zrušit + +gui.map.info=Verze: {0} - Refresh: {1}/{2} +gui.map.loading=Načítání +gui.map.waiting=Pozastaven {0} +gui.map.running=Aktivní {0} + +# Modules +module.instructions.title=Instrukce +module.kill_n_collect.status=Zabíjení: {0} - Sběr: {1} + +module.map_travel.status.has_next=Cestování na {0}, Další mapa: {1} +module.map_travel.status.no_next=Další mapa {0} + +module.disconnect.status=Odpojování ({0}): {1} +module.disconnect.status_stopped=Odpojeno ({0}) +module.disconnect.status_paused=Pauza z důvodu ({0}), znovu se připojí za {1} +module.disconnect.reason.break=Pauza +module.disconnect.reason.honor=Ztracená čest +module.disconnect.reason.draw_fire=Nepřítel použil přesměrování palby + +# Players +gui.players.not_found.title=Uživatel nenalezen +gui.players.not_found.by_name=Uživatel s jménem {0} nebyl nalezen\nZkus použít raději ID hráče. +gui.players.not_found.by_id=Uživatel s ID {0} nebyl nalezen. +# Issues +bot.issue.module_load_failed=Chyba při načtení modulu {0}, Použije se základní +bot.issue.config_load_failed=Chyba při načtení configu.\nbot použije základní config a nebude uložen.\nOdstraň soubor config.json (vymaže nastavení) & restartuj bota. +bot.issue.feature.failed_to_tick=Něco se pokazilo. +bot.issue.feature.failed_to_load=Plugin nebyl načten +bot.issue.feature.failed_to_load.desc={0} nemohl být načten pořádně: {1} + +bot.issue.resolution=Změna roulišení je na
Vlastní nebezpečí!
Pokud budete mít potíže,
nastavte základní. +# Verifier +verifier.discord_warn.title=Join the official discord! +verifier.discord_warn.content.1=Darkbot je zdarma, pokud za něj zaplatíte, byl jste podveden! +verifier.discord_warn.content.2=Ujistěte. +verifier.discord_warn.checkbox.dont_show=Nezobrazovat tuhle zprávu +verifier.discord_warn.button.join_discord=Připojit na discord +verifier.discord_warn.button.ignore=Ignorovat + +verifier.authenticate.error=Bot se nezapl, zkus restartovat PC. +verifier.authenticate.title=Discord ověření +verifier.authenticate.content=Postupujte podle pokynů v prohlížeči, tohle okno nezavírejte!\nOvěření je lokální a neposílá žádné informace na servery.\nPokud se stránka nenačte, zkus použít Chrome nebo deaktivuj Antivirus. + +verifier.panel.invalid_auth=Neplatné ověření, zkus restartovat bota. +verifier.panel.authenticated.no_server=Byl jsi ověřen jako {0}
Nejsi na žádném serveru. +verifier.panel.authenticated.servers=Byl si pŕihlášen jako {0}, Tvé servery:
    {1}
+verifier.panel.authenticated.join_official=Měl by jsi se připojit na discord pro všechny aktualizace!
+verifier.panel.authenticated.blacklisted=Jsi členem discordu který je na blacklistu kvůli sdílení práce popcorna bez jeho souhlasu.
Musíš Opustit blacklist servery abys mohl použít bota. Až odejdeš, klikni na Update.
+ +# Language changed +language.changed.title=Změna jazyku provedena +language.changed.content=Změna jazyku provedena na {0} Vytvořena uživateli: {1}.\nRestartuj bota pro projevení všech změn. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@Haeth#7521 & @RCKSTRZKI#3305 \ No newline at end of file diff --git a/src/main/resources/lang/strings_de.properties b/src/main/resources/lang/strings_de.properties new file mode 100644 index 00000000..edf8d064 --- /dev/null +++ b/src/main/resources/lang/strings_de.properties @@ -0,0 +1,115 @@ +config.general=Allgemein +config.general.current_module=Module +config.general.working_map=Working map +config.general.offensive=Offensive Konfig +config.general.offensive.desc=Wird verwendet, um NPCs zu töten +config.general.roam=Flucht Konfig +config.general.roam.desc=Wird verwendet, um auf der Karte nach NPCs zu suchen +config.general.run=Flucht Konfig +config.general.run.desc=Wird verwendet, um in Sicherheitszonen zu fliehen oder Karten zu wechseln +config.general.formation_check= Drohnen-Formation prüfen +config.general.formation_check.desc=Prüft Drohnen-Formation alle X Sekunden, setze 0 für Deaktivierung +config.general.safety=Sicherheit +config.general.safety.repair_hp_range=Halte Leben zwischen +config.general.safety.repair_hp_range.desc=Selbst wenn ein NPC geschossen wird, fliehen wenn HP darunter fällt +config.general.safety.repair_hp_no_npc=Repariere wenn die HP beim Fliegen darunter fällt +config.general.safety.repair_hp_no_npc.desc=Die Hitpoints um zu flüchten wenn der Bot sich in der Roam Config befindet +config.general.safety.repair_to_shield=Repariere Schild bis +config.general.safety.repair_to_shield.desc=Minimum Schild bis repariert wird +config.general.safety.repair=Reparier Konfig +config.general.safety.repair.desc=Wird benutzt um in die Reperatur Konfi zu wechseln +config.general.safety.max_deaths=Max Tode +config.general.safety.max_deaths.desc=Wenn die maximalen Tode erreicht wurden, pausiert der bot +config.general.safety.revive_location=Wiederbelebunggort +config.general.safety.revive_location.desc=Setzt den Wiederbelebungsort +config.general.safety.wait_before_revive=Warte vor dem Wiederbeleben (Sek) +config.general.safety.wait_before_revive.desc=Wartete X Sekunden bevor das Schiff wiederbelebt wird +config.general.safety.wait_after_revive=Warte nach der Wiederbelebung (Sek) +config.general.safety.wait_after_revive.des=Zeit die nach der Wiederbelebung gewartet wird um das Schiff zu reparieren +config.general.running=Fliehen +config.general.running.run_from_enemies=Fliehe von Gegnern +config.general.running.run_from_enemies.desc=Flieht von Gegnern die dich angreifen oder vor kurzem angegriffen haben +config.general.running.remember_enemies_for=Gegner merken für +config.general.running.remember_enemies_for.desc=Wie lange der Bot sich die Gegner merken soll +config.general.running.run_from_enemies_sight=Fliehe von Gegnern in Sichtweite +config.general.running.run_from_enemies_sight.desc=Flieht vom Gegner, wenn dieser auf der Minimap auftaucht +config.general.running.stop_running_no_sight=Stoppe Fliehen wenn Gegner außerhalb deiner Sicht ist +config.general.running.stop_running_no_sight.desc=Stoppt fliehen, wenn Gegner außerhalb deiner Sichtweite oder aufgehört hat dich anzugreifen. +config.general.running.max_sight_distance=Max Sichtweite +config.general.running.max_sight_distance.desc=Prüft Gegner nicht außerhalb dieser Distanz +config.general.running.ship_ability=Schiffsfähigkeit Taste +config.general.running.ship_ability.desc=Wird aktiviert wenn er von Gegnern flieht +config.general.running.ship_ability_min=Schiffsfähigkeit minimalste Entfernung +config.general.running.ship_ability_min.desc=Minimale Distanz, wann die Fähigkeit verwendet werden soll +config.general.running.run_furthest_port=Näheste Portal Distanz max +config.general.running.run_furthest_port.desc=Flieht zum nähesten Portal, wenn angegriffen und dieses nicht weiter weg ist als die eingetragene Distanz +config.general.roaming=Fliegen & bevorzugtes Gebietsverhalten +config.general.roaming.keep=Fliege weiter zum selben Punkt bis dieser erreicht wurde +config.general.roaming.sequential=Fliege zu beforzugten Bereichen der Reihe nach +config.general.roaming.only_kill_preferred=Töte NPCs nur in den beforzugten Bereichen +config.general.roaming.only_kill_preferred.desc=Greift nur NPCs an, wenn diese in einem bevorzugten Bereich sind. + +config.collect=Sammler +config.collect.stay_away_from_enemies=Von Gegnern fernhalten +config.collect.auto_cloack=Auto Tarnung +config.collect.auto_cloack_key=Auto Tarnungs Taste +config.collect.radius=Sammelradius während NPCs abgeschossen werden +config.collect.radius.desc=Ressourcen-Sammelradius beim Töten von NPCs + +config.loot=NPC killer +config.loot.sab=Sab +config.loot.sab.desc=Auto Sab NPCs um länger zu überleben +config.loot.sab.enabled=Aktivieren +config.loot.sab.key=Sab-Taste +config.loot.sab.percent=Schiff unter +config.loot.sab.npc_amount=NPC min Schild +config.loot.ammo_key=Munitions Taste +config.loot.ship_ability=Offensive Schiffsfähigkeit Taste +config.loot.ship_ability_min=Offensive Schiffsfähigkeit min NPC Leben +config.loot.ship_ability_min.desc=Minimalstes NPC Leben um die Fähigkeit zu aktivieren. +config.loot.max_circle_iterations=Intelligente Kreiseländerung +config.loot.max_circle_iterations.desc=Maximale Bewegungen um das kreiseln vorauszuschauen, 0 zur Deaktivierung, Empfohlen 3-5 +config.loot.run_config_in_circle=Flucht Konfig zur Verfolgung +config.loot.run_config_in_circle.desc=Verwendet die Flucht Konfig um fliehende NPCs zu erwischen +config.loot.group_npcs=Gruppiere gleiche NPCs +config.loot.group_npcs.desc=Gruppiert NPCs im gleichen GG +config.loot.npc_distance_ignore=Ignoriert NPCs unter + +config.pet=PET +config.pet.enabled=PET benutzen +config.pet.enabled.desc=Wenn diese Funktion deaktiviert ist, ignoriert der Bot alle PET funktionen (auch in Plugins!) +config.pet.module=# verwende PET Modul +config.pet.module.desc=0 - Passiv-Modus
1 - Schutz-Modus
2 - Unterschiedlich (je nach Ausrüstung) + +config.miscellaneous=Sonstiges +config.miscellaneous.refresh_time=Aktualisiere alle +config.miscellaneous.refresh_time.desc=Lädt Spielseite alle X Minuten neu (0 zur Deaktivierung) +config.miscellaneous.pause_for=Pause nach Aktualisierung +config.miscellaneous.pause_for.desc=Macht nach Aktualisierung der Spielseite X Minuten Pause (0 zur Deaktivierung) +config.miscellaneous.drone_repair_percentage=Repariere Drohnen ab +config.miscellaneous.drone_repair_percentage.desc=Reapariert Drohnen wenn diese über X % Schaden nehmen + +config.bot_settings=Bot Einstellungen +config.bot_settings.locale=Sprache +config.bot_settings.zone_resolution=Zonengenauigkeit +config.bot_settings.zone_resolution.desc=Anzahl der Kartenunterteilungen wenn die Zone ausgewählt wurde. +config.bot_settings.map_start_stop=Start/Pause klick auf Map (Botmap) +config.bot_settings.map_start_stop.desc=Linkslick Start/Pause. Rechtsklick um Schiff zu bewegen. +config.bot_settings.confirm_exit=Schließen Bestätigen +config.bot_settings.confirm_exit.desc=Schließen des Bots muss bestätigt werden +config.bot_settings.min_tick=Minimum Tick Zeit +config.bot_settings.dev_stuff=Entwickerloptionen zeigen +config.bot_settings.dev_stuff.desc=Beim aktivieren wird der Bot mehr CPU-Leistung beanspruchen. +config.bot_settings.display=Anzeige +config.bot_settings.display.show_names=Spielernamen anzeigen +config.bot_settings.display.show_names.desc=Zeigt alle Spielernamen auf der Map (Botmap) +config.bot_settings.display.hide_name=Name verstecken +config.bot_settings.display.hide_name.desc=Versteckt eigenen Namen in der Map (Botmap) +config.bot_settings.display.trail_length=Wegspur +config.bot_settings.display.trail_length.desc=Gibt in Sekunden an wie lange die weiße Wegspur rückwirkend angezeigt werden soll. +config.bot_settings.display.show_zones=Zeige ausgewählte Zonen in der Map +config.bot_settings.display.show_zones.desc=Aktivieren um avoided/preferred zones (vermiedenen/bevorzugte Zonen) auf der Map anzuzeigen +config.bot_settings.display.button_size=GUI Button Größe +config.bot_settings.display.button_size.desc=Ändert die Größe der Map auf der GUI (update: Tab in Konfig ändern & Größe des Hauptfensters ändern) + +translation.credit=@[DE/EN] Eatforyou / Tim#6666, @Ex0b1te#7707 & @PackZ#3856 \ No newline at end of file diff --git a/src/main/resources/lang/strings_el.properties b/src/main/resources/lang/strings_el.properties new file mode 100644 index 00000000..a937a21f --- /dev/null +++ b/src/main/resources/lang/strings_el.properties @@ -0,0 +1,286 @@ +# Config tabs +tabs.general=Γενικά +tabs.general.desc=Γενικες ρυθμίσεις του Bot +tabs.preferred_zones=Προτιμώμενες Ζώνες +tabs.preferred_zones.desc=Ζώνες η όποιες το Bot θα περιπλανιέται τυχαία +tabs.avoided_zones=Ζώνες Αποφύγεις +tabs.avoided_zones.desc=Ζώνες που αντιμετωπίζονται ως εμπόδια για το Bot +tabs.safety_places=Ζώνες Aσφάλειας +tabs.safety_places.desc=Ζώνες Aσφάλειας που το Bot θα τρέξει για επισκευή η από εχθρούς +tabs.plugins.desc=Πληροφορίες και Διαμόρφωσης για τα εγκατεστημένα Plugins + +# Config tree +config.general=Γενικά +config.general.current_module=Εξάρτημα +config.general.working_map=Χάρτης +config.general.offensive=Επιθετική διαμόρφωση +config.general.offensive.desc=Χρησιμοποιείται για να σκοτώσει NPC (Βαρυα) +config.general.roam=Διαμόρφωση περιπλάνησης +config.general.roam.desc=Χρησιμοποιείται όταν ψάχνει για NPC η περιπλανιέται στον χάρτη +config.general.run=Διαμόρφωση διαφυγής +config.general.run.desc=Χρησιμοποιείται για να τρέξει στην πλησιέστερη ζώνη ασφάλειας η στην αλλαγή του χάρτη +config.general.formation_check=Έλεγξε διάταξη των UAV +config.general.formation_check.desc=Ελέγχει την διάταξη τον ρομποτικών σκαφών κάθε Χ δευτερόλεπτα (Για απενεργοποίηση βαλτέ 0) +config.general.safety=Ασφάλεια +config.general.safety.repair_hp_range=Κράτα την ζωή μεταξύ +config.general.safety.repair_hp_range.desc=Ακόμη όταν σκοτώνει ένα NPC και βρίσκεται κάτω από αυτό το ποσοστό ζωής τότε θα τρέξει στην πλησιέστερη ζώνη ασφάλειας +config.general.safety.repair_hp_no_npc=Επισκευή όταν περιπλανιέται & ζωή είναι κάτω από +config.general.safety.repair_hp_no_npc.desc=Ποσοστό ζωής κάτω από το οποίο το σκάφος θα πάει για επισκευή ακόμη και όταν άπλα περιπλανιέται στον χάρτη +config.general.safety.repair_to_shield=Επισκευή της ασπίδας μέχρι +config.general.safety.repair_to_shield.desc=Ελάχιστη ασπίδα για να σταματήσει η επισκευή +config.general.safety.repair=Διαμόρφωση επισκευής +config.general.safety.repair.desc=Χρησιμοποιείται μετά την επισκευή των ασπίδων στην διαμόρφωση διαφυγής +config.general.safety.max_deaths=Μέγιστο αριθμό θανάτων +config.general.safety.max_deaths.desc=Μέγιστος αριθμό θανάτων, αν επιτευχθεί, τότε το Bot κάνει παύση +config.general.safety.revive_location=Τοποθεσία επισκευής +config.general.safety.revive_location.desc=Που να επισκευαστεί το σκάφος +config.general.safety.revive_location.list.base=Βάση +config.general.safety.revive_location.list.portal=Πύλη +config.general.safety.revive_location.list.spot=Σημείο +config.general.safety.wait_before_revive=Χρόνος πρίν την επισκευή +config.general.safety.wait_before_revive.desc=Δευτερόλεπτα πρίν την επισκευή +config.general.safety.wait_after_revive=Αναμονή μετά την επισκευή +config.general.safety.wait_after_revive.des=Δευτερόλεπτα που να περιμένει το Bot μετά από την επισκευή +config.general.running=Διαφυγή +config.general.running.run_from_enemies=Τρέξε από τους εχθρούς +config.general.running.run_from_enemies.desc=Τρέχει από τους εχθρούς που σε πυροβολούν η που σε έχουν πυροβολήσει πρόσφατα +config.general.running.remember_enemies_for=Να θυμάται τους εχθρούς για +config.general.running.remember_enemies_for.desc=Για πόση ώρα να τρέχει από εχθρούς εντός ορατότητας που σου έχουν επιτεθεί πιο πρίν (Δευτερόλεπτα) +config.general.running.run_from_enemies_sight=Τρέξε από εχθρούς στην ορατοτητα +config.general.running.run_from_enemies_sight.desc=Τρέχει από τους εχθρούς αμέσως μόλις μπουν στον μίνι χάρτη και τις Χ απόστασης ορατότητας +config.general.running.stop_running_no_sight=Σταματα να τρεχεις όταν είσαι έκτος ορατότητας +config.general.running.stop_running_no_sight.desc=Θα σταματήσει να τρέχει όταν ο εχθρός έχει σταματήσει να επιτίθεται και δεν είναι πλέον στην ορατοτητα του Bot +config.general.running.max_sight_distance=Μεγιστη απόσταση ορατότητας +config.general.running.max_sight_distance.desc=Δεν τους θεωρεί εχθρούς πλέον αν είναι μακριά από X απόσταση ορατότητας +config.general.running.ship_ability=Ικανότητα σκάφους +config.general.running.ship_ability.desc=Χρησιμοποιείται όταν τρέχει από έναν η πολλούς εχθρούς που πυροβολάνε +config.general.running.ship_ability_min=Ικανότητα σκάφους ελαχίστης απόστασης +config.general.running.ship_ability_min.desc=Ελάχιστη απόσταση από την πύλη για να χρησιμοποίηση την ικανότητα +config.general.running.run_furthest_port=Πλησιέστερη απόσταση από την πύλη μέγιστο +config.general.running.run_furthest_port.desc=Διαφυγή στην πύλη που είναι πιο μακριά από τον εχθρό έκτος και αν η πύλη είναι σε λιγότερη από συγκεκριμένη απόσταση +config.general.roaming=Συμπεριφορά κατά την περιπλάνηση στις προτιμώμενες περιοχές και γενικότερα στον χάρτη +config.general.roaming.keep=Συνέχισε να πηγαίνεις στην ίδια διαδρομή μέχρι να φτάσεις +config.general.roaming.sequential=Πήγαινε στις προτιμώμενες ζώνες με τη σειρά +config.general.roaming.only_kill_preferred=Σκότωνε NPC μόνο στην προτιμώμενη ζώνη +config.general.roaming.only_kill_preferred.desc=Να κλειδώνει και να σκοτώνει μόνο npc που βρίσκονται μέσα σε προτιμώμενη ζώνη + +config.collect=Συλλογή +config.collect.stay_away_from_enemies=Μείνε μακριά από εχθρούς +config.collect.auto_cloack=Αυτόματο καμουφλάρισμα +config.collect.auto_cloack_key=Πλήκτρο καμουφλαρίσματος +config.collect.radius=Απόσταση συλλογής κατά την εξόντωση +config.collect.radius.desc=Απόσταση συλλογής πρώτων υλών κατά την εξόντωση των NPC + +config.collect.box_table.resource=Πόροι +config.collect.box_table.resource.desc=Κουτιά η άλλοι πόροι που έχει δει το Bot +config.collect.box_table.collect=Σύλλεξε +config.collect.box_table.collect.desc=Εάν θέλετε να συλλεγη αυτον τον πόρο +config.collect.box_table.wait=Περίμενε (ms) +config.collect.box_table.wait.desc=Χρόνος ο οποίοι το Bot πρέπει να περιμένει για να συλλέξει αυτον τον πόρο (Σε χιλιοστα του δευτερολεπτου) +config.collect.box_table.priority=Προτεραιότητα +config.collect.box_table.priority.desc=Προτεραιότητα #1 θα συλλεχθεί πρίν από την προτεραιότητα #2 + +config.loot=Εξολοθρευτής NPC +config.loot.sab=SAB +config.loot.sab.desc=Αυτόματα χρησιμοποιεί SAB στα NPC +config.loot.sab.enabled=Ενεργοποίηση +config.loot.sab.key=Πλήκτρο +config.loot.sab.percent=Ελάχιστη ασπίδα του σκάφους +config.loot.sab.npc_amount=Ελάχιστη ασπίδα του NPC +config.loot.rsb=RSB +config.loot.rsb.desc=Αυτόματα χρησιμοποιεί RSB στα NPC (Πρέπει να επιλέξετε σε ποια NPC να κάνει χρήση στα Έξτρα) +config.loot.rsb.enabled=Ενεργοποίηση +config.loot.rsb.key=Πλήκτρο +config.loot.rsb.ammo_refresh=RSB χρόνος επαναφόρτισης +config.loot.ammo_key=Πλήκτρο πυρομαχικών +config.loot.ship_ability=Επιθετική ικανότητα πλήκτρο +config.loot.ship_ability_min=Επιθετική ικανότητα ελάχιστη ζωή +config.loot.ship_ability_min.desc=Ελάχιστη ζωή που πρέπει να έχει το NPC ώστε το Bot να χρησιμοποιήσει την επιθετική ικανότητα του σκάφους +config.loot.max_circle_iterations=Επαναλήψεις έξυπνης περιστροφής +config.loot.max_circle_iterations.desc=Μέγιστος αριθμός κινήσεων που θα πρόβλεψη το Bot να κάνει ώστε να αλλάξει την φόρα περιστροφής γύρω από το NPC (Για απενεργοποίηση βαλτέ 0) (Συνιστάται 3-5) +config.loot.run_config_in_circle=Γρήγορη διαμόρφωση για να κυνηγήσει +config.loot.run_config_in_circle.desc=Χρησιμοποιεί την Γρήγορη διαμόρφωση για να κυνηγήσει τα NPC που διαφεύγουν +config.loot.group_npcs=Ομαδοποίηση παρόμοιων NPC +config.loot.group_npcs.desc=Oμαδοποιεί τα NPC μιας γαλαξιακής πύλης στον πινάκα με τα παρόμοια NPC +config.loot.npc_distance_ignore=Aγνόησε NPC παραπέρα από + +config.loot.npc_table.name=Όνομα +config.loot.npc_table.name.desc=NPC ονόματα (NPC που τελειώνουν με * είναι ομαδοποιημένα) +config.loot.npc_table.radius=Απόσταση +config.loot.npc_table.radius.desc=Απόσταση που να κρατήσει απτό NPC +config.loot.npc_table.priority=Προτεραιότητα +config.loot.npc_table.priority.desc=Προτεραιότητα #1 θα στοχεύετε πρίν από την προτεραιότητα #2 +config.loot.npc_table.kill=Σκότωσε +config.loot.npc_table.kill.desc=Εάν θέλετε να σκοτώνει αυτό το NPC +config.loot.npc_table.attack_key=Πυρομαχικά +config.loot.npc_table.attack_key.desc=Ειδικα πυρομαχικά για αυτό το NPC (Εάν είναι κενό τότε χρησιμοποιεί προεπιλεγμενα πυρομαχικά) +config.loot.npc_table.attack_formation=Διαμόρφωση +config.loot.npc_table.attack_formation.desc=Ειδική διαμόρφωση γι'αυτό το NPC. (Εάν είναι κενό τότε χρησιμοποιεί προεπιλεγμένη Επιθετική διαμόρφωση) +config.loot.npc_table.extra=Έξτρα +config.loot.npc_table.extra.desc=Επιπλέον έξτρα επιλογες για τα NPC + +config.loot.npc_table.extra.no_circle=Χωρίς κύκλο +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=Δεν θα κάνει κύκλους γύρω από το NPC θα παραμείνει μέσα στην απόσταση που δόθηκε +config.loot.npc_table.extra.ignore_ownership=Αγνόηση ιδιοκτησίας +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Συνεχίζει να πυροβολάει το NPC ακόμα κι αν έχει λευκό κύκλο +config.loot.npc_table.extra.ignore_attacked=Aγνόησε επίθεση +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Κλείδωσε το NPC ακόμα και αν άλλοι παίχτες το πυροβολούν +config.loot.npc_table.extra.passive=Παθητική +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Παθητική συμπεριφορά απέναντι σε αυτό το NPC έκτος αν επιτεθεί +config.loot.npc_table.extra.attack_second=Πυροβόλα δεύτερος +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Bαράει μόνο αν άλλοι επιτίθενται ηδη αυτό το NPC.
Πρέπει να επιλέξετε "Aγνόησε επίθεση" & "Αγνόηση ιδιοκτησίας" +config.loot.npc_table.extra.use_rsb=Xρήση RSB +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Xρήση RSB πυρομαχικών για να επιτεθεί αυτό το NPC + +config.pet=Pet +config.pet.enabled=Xρήση Pet +config.pet.enabled.desc= Aν απενεργοποιηθεί, τo Βot θα αγνόει ολες τις λειτουργίες του Pet +config.pet.module=Νούμερο εξαρτήματος +config.pet.module.desc=0 - Παθητική κατάσταση 1 - Αμυντική κατάσταση 2 - Οτιδήποτε άλλο είναι μετά + +config.miscellaneous=Διάφορο +config.miscellaneous.refresh_time=Ανανέωση κάθε +config.miscellaneous.refresh_time.desc=Ανανεώνει την σελίδα του παιχνιδιού κάθε X λεπτά (Συνιστάται 60 λεπτά) +config.miscellaneous.pause_for=Παύση μετά την ανανέωση +config.miscellaneous.pause_for.desc=Παύση Χ λεπτά μετά την ανανέωση +config.miscellaneous.drone_repair_percentage=Επισκευή των UAV +config.miscellaneous.drone_repair_percentage.desc=Tα ρομποτικά σκάφη επισκευάζονται αν η ζημία τους υπερβεί αυτό το ποσοστό + +config.bot_settings=Ρυθμίσεις +config.bot_settings.locale=Γλώσσα +config.bot_settings.zone_resolution=Ακρίβεια ζώνης +config.bot_settings.zone_resolution.desc=Αριθμός υποδιαιρέσεων του χάρτη κατά την επιλογή ζωνών έτσι ώστε να είναι πιο ακριβείς +config.bot_settings.map_start_stop=Εκκίνηση/Παύση με κλικ +config.bot_settings.map_start_stop.desc=Αριστερό κλικ για την Εκκίνηση/Παύση του Bot & Δεξί κλικ για να μετακινήσετε το σκάφος +config.bot_settings.confirm_exit=Επιβεβαίωση στην έξοδο +config.bot_settings.confirm_exit.desc=Επιβεβαίωση κατά την έξοδο για τον τερματισμό του Bot +config.bot_settings.min_tick=Ελάχιστος χρόνος ανταποκρίσεις +config.bot_settings.dev_stuff=Λειτουργία Developer +config.bot_settings.dev_stuff.desc=Ενεργοποιώντας το επιβαρύνεται περισσότερο το Η/Υ. +config.bot_settings.display=Προβολή +config.bot_settings.display.show_names=Ενφάνιση ονομάτων παικτών +config.bot_settings.display.show_names.desc=Εμφανίζει ονόματα παικτών στο χάρτη του Bot +config.bot_settings.display.hide_name=Απόκρυψη ονόματος +config.bot_settings.display.hide_name.desc=Κρύβει το όνoμα του χρήστη στο χάρτη του Bot +config.bot_settings.display.trail_length=Μήκος ιχνών +config.bot_settings.display.trail_length.desc=Το διάστημα το οποίο θα φαίνεται η διαδρομή που ακολούθησε το σκάφος στον χάρτη του Bot (Σε δευτερόλεπτα) +config.bot_settings.display.show_zones=Δειξε τις Ζώνες στον χάρτη +config.bot_settings.display.show_zones.desc=Δείχνει τις Ζώνες (Αποφυγής & Προτιμώμενος) στον χάρτη του Bot +config.bot_settings.display.button_size=Μέγεθος τον κουμπιών στο Bot +config.bot_settings.display.button_size.desc=Μεγαλώνει/Μικραίνει τα κουμπιά στο Bot (Για εφαρμογή αλλάξτε Καρτέλα/Μέγεθος στο Bot) + +# Safety places +safety_places.type.portal=Πύλη +safety_places.type.cbs=Σταθμός συμμαχίας +safety_places.type.base=Βάση + +safety_places.run_mode= [Όλα] Συνθήκη χρήσης +safety_places.run_mode.always=Πάντα +safety_places.run_mode.enemy_flee_only=Διαφυγή (Εχθρός κοντά) +safety_places.run_mode.repair_only=Επισκευή (Όχι εχθροί) +safety_places.run_mode.refresh=Ανανέωση +safety_places.run_mode.never=Πότε + +safety_places.jump_mode= [Πύλη] Συνθήκη χρήσης +safety_places.jump_mode.never=Πότε +safety_places.jump_mode.escaping=Διαφυγή (Εχθρός επιτίθεται) +safety_places.jump_mode.fleeing=Διαφυγή (Εχθρός κοντά) +safety_places.jump_mode.repairing=Επισκευή +safety_places.jump_mode.always=Πάντα +safety_places.jump_mode.always_other_side=Πάντα (Επισκευή στην αλλη πλευρα) + +safety_places.cbs_mode= [Σταθμός συμμαχίας] Συνθήκη χρήσης +safety_places.cbs_mode.ally=Φιλικός Σταθμός συμμαχίας +safety_places.cbs_mode.ally_neutral=Συμμαχικό η άδειο + +# Plugins +plugins.reload=Ανανέωση των Plugins +plugins.config_button.enabled.desc=Εμφάνιση ρυθμίσεων +plugins.config_button.disabled.desc=Η επέκταση είναι απενεργοποιημένη, η λειτουργία δεν φορτώθηκε +plugins.config_button.popup=Αδυνατεί επεξεργασία ρυθμίσεων +plugins.config_button.popup.desc=Η ρυθμίσεις δεν είναι εφικτές για μη φορτωμένες λειτουργίες \nEνεργοποιήστε τη λειτουργία μετά φορτώστε τη και ξαναπροσπαθήστε. + +plugins.issues.invalid_json=Μη αποδεκτό plugin.json +plugins.issues.invalid_json.desc=Η κατώτερη έκδοση {0} είναι ανώτερη από την υποστηριζόμενη έκδοση {1} +plugins.issues.bot_update=Απαιτείτε ενημέρωση του Bot +plugins.issues.bot_update.desc=Αυτό το Plugin απαιτεί {0}, έτσι δεν µπορεί να τρέξει στο DarkBot v{1} +plugins.issues.plugin_update=Το Plugin µπορεί να χρειάζεται ενημέρωση +plugins.issues.plugin_update.desc=Το Plugin έχει φτιαχτεί για {0}, έτσι µπορεί να μην λειτουργήσει στο DarkBot v{1} + +plugins.issues.signature.plugin_not_signed=Το Plugin δεν είναι Υπογεγραμμένο +plugins.issues.signature.plugin_not_signed.desc=Το Plugin δεν έχει υπογραφή η αυτή που έχει είναι μη έγκυρη +plugins.issues.signature.unknown_signature=Άγνωστη υπογραφή +plugins.issues.signature.unknown_signature.desc=Αυτό το Plugin έχει υπογραφή από μη έμπιστο πιστοποιητικού +plugins.issues.signature.invalid_signature=Μη έγκυρη υπογραφή +plugins.issues.signature.invalid_signature.desc= Το Plugin έχει μη έγκυρη υπογραφή η έχει αλλοιωθεί + +# Main GUI +gui.hamburger_button.home=ΑΡΧΙΚΗ +gui.hamburger_button.reload=ΑΝΑΝΕΩΣΗ +gui.hamburger_button.copy_sid=ΑΝΤΙΓΡΑΦΗ SID +gui.hamburger_button.discord=DISCORD +gui.config_button=Εμφάνιση/Απόκρυψη Διαμόρφωσης +gui.start_button=Εκκίνηση/Παύση Bot +gui.visibility_button=Εμφάνιση/Απόκρυψη Browser +gui.pin_button=Πάντα στην κορυφή + +gui.exit=Είστε σίγουρη ότι θέλετε να κλείσετε το Bot? +gui.exit.desc=Επικύρωση κλεισίματος του Βot, µπορεί να καταργηθεί στις Ρυθμίσεις > Ρυθμίσεις Bot +gui.exit.yes_button=Ναι +gui.exit.cancel_button=Ακύρωση + +gui.map.info=v{0} - Ανανέωση: {1}/{2} +gui.map.loading=Φόρτωση +gui.map.waiting=ΑΝΑΜΟΝΗ {0} +gui.map.running=ΤΡΕΧΕΙ {0} + +# Modules +module.instructions.title=Οδηγίες +module.kill_n_collect.status=Εξόντωση: {0} - Συλλογή: {1} + +module.map_travel.status.has_next=Ταξιδεύοντας προς {0}, επόμενος χάρτης: {1} +module.map_travel.status.no_next=Ταξιδεύοντας προς {0} + +module.disconnect.status=Αποσύνδεση ({0}): {1} +module.disconnect.status_stopped=Αποσυνδεδεμένος ({0}) +module.disconnect.status_paused=Σε παύση ({0}), Επανασύνδεση σε {1} +module.disconnect.reason.break=Διάλειμμα +module.disconnect.reason.honor=Χαμένη τιμή + +# Issues +bot.issue.module_load_failed=Αποτυχία στη φόρτωση του Module {0}, χρησιμοποίηση του default (βασικου) +bot.issue.config_load_failed=Αποτυχία στη φόρτωση του αρχείου της διαμόρφωσης.\nΤο Bot θα χρησιμοποιήσει την βασική διαμόρφωση και δεν θα γίνει αποθήκευση.\nΔιαγράψτε το αρχείο config.json & κάντε επανεκκίνηση το Bot (Θα σβήσει τις ρυθμίσεις). +bot.issue.feature.failed_to_tick=Αποτυχία στο Τick +bot.issue.feature.failed_to_load=Αποτυχία φορτώσεις χαρακτηριστικού +bot.issue.feature.failed_to_load.desc={0} Αδυναμία σωστής καταχωρήσεις: {1} + +# Verifier +verifier.discord_warn.title=Μπείτε στο επίσημο Discord Server! +verifier.discord_warn.content.1=Το Bot είναι δωρεάν, εάν έχετε πληρώσει ή έχετε παρακολουθήσατε διαφημίσεις τότε πιαστήκατε κορόιδο! +verifier.discord_warn.content.2=Βεβαιωθείτε οτι βρίσκεστε στο επίσημο Discord Server για να λάβετε τις τελευταίες ενημερώσεις δωρεάν. +verifier.discord_warn.checkbox.dont_show=Να μην εμφανίζεται ξανά αυτό το μήνυμα +verifier.discord_warn.button.join_discord=Μπείτε στο Discord +verifier.discord_warn.button.ignore=Αγνόησε + +verifier.authenticate.error=Το Bot δεν κατάφερε να ξεκινήσει. Δοκιμάστε να κάνετε επανεκκίνηση τον Y/H σας. +verifier.authenticate.title=Discord ταυτοποίηση +verifier.authenticate.content=Ακολουθήστε τις οδηγίες του Browser σας, μην κλείσετε αυτό το παράθυρο.\nΟ έλεγχος ταυτοποίησης είναι τοπική και δεν θα στείλει δεδομένα σε κανένα Server.\nΕάν η σελίδα δεν φόρτωση στο Browser σας, δοκιμάστε να χρησιμοποιήσετε το Google Chrome ή να απενεργοποιήσετε το Anti-Virus. + +verifier.panel.invalid_auth=Μη έγκυρος έλεγχος ταυτότητας, δοκιμάστε να κάνετε επανεκκίνηση του Bot +verifier.panel.authenticated.no_server=Έχετε πιστοποιηθεί ως {0}
Δεν φαίνεται να βρίσκεστε σε κανένα Server. +verifier.panel.authenticated.servers=Έχετε πιστοποιηθεί ως {0}, μέλος:
    {1}
+verifier.panel.authenticated.join_official=Θα πρέπει να εγγραφείτε στον επίσημο Server για να λάβετε τις πιο πρόσφατες ενημερώσεις!
+verifier.panel.authenticated.blacklisted=Είστε μέλος των Servers που βρίσκονται στη μαύρη λίστα και που αναδιανέμουν το Bot χωρίς τη συγκατάθεση μας.
Πρέπει να φύγετε από τους Servers που βρίσκονται στη μαύρη λίστα για να χρησιμοποιήσετε το Bot! Κάντε κλικ για ενημέρωση όταν αποχωρήσετε από το Server.
" + +# Language changed +language.changed.title=Η Γλώσσα του Bot άλλαξε! +language.changed.content=Το Βot τώρα χρησιμοποιεί μια μεταφραση στα {0} επιμελημένη από {1}. \nΕπικοινωνήστε με {1} στο Discord για διορθώσεις, ευχαριστώ. \nΠροτείνεται να γινει επανεκκίνηση του Bot ώστε να εφαρμοστούν πλήρως όλες οι αλλαγές. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@❆ 𝐄𝐧𝐯𝐲#2737 & @Lamborghini#7684 \ No newline at end of file diff --git a/src/main/resources/lang/strings_en.properties b/src/main/resources/lang/strings_en.properties new file mode 100644 index 00000000..c178974d --- /dev/null +++ b/src/main/resources/lang/strings_en.properties @@ -0,0 +1,366 @@ +# Config tabs +tabs.general=General +tabs.general.desc=General settings for the bot +tabs.zones=Map Zones +tabs.zones.desc=Select preferred, avoided, and safety zone settings +tabs.preferred_zones=Preferred Zones +tabs.preferred_zones.desc=Zones the bot will randomly roam to +tabs.avoided_zones=Avoided Zones +tabs.avoided_zones.desc=Zones treated as barriers by the bot +tabs.safety_places=Safety places +tabs.safety_places.desc=Safe places to run to for repairing or escaping enemies +tabs.players=Players +tabs.players.desc=Player tab to manage other dark orbit players or accounts +tabs.plugins=Plugins +tabs.plugins.desc=Info and configs for installed plugins + +# Config tree +config.general=General +config.general.current_module=Module +config.general.working_map=Working map +config.general.offensive=Offensive config +config.general.offensive.desc=Used to kill NPCs +config.general.roam=Roam config +config.general.roam.desc=Used to roam around the map, searching for NPCs +config.general.run=Run config +config.general.run.desc=Used to run to safety or switch around maps +config.general.formation_check=Check formation +config.general.formation_check.desc=Checks formation every X seconds, set 0 to disable +config.general.safety=Safety +config.general.safety.repair_hp_range=Keep health between +config.general.safety.repair_hp_range.desc=Even if shooting an npc, run away when under this hp +config.general.safety.repair_hp_no_npc=Repair when roaming & hp under +config.general.safety.repair_hp_no_npc.desc=Hp to run at when just roaming the map +config.general.safety.repair_to_shield=Repair until shield +config.general.safety.repair_to_shield.desc=Minimum shield to stop repairing +config.general.safety.repair=Repair config +config.general.safety.repair.desc=Used to repair after run formation shields are full +config.general.safety.max_deaths=Max deaths +config.general.safety.max_deaths.desc=Max amount of deaths, if reached, bot is paused +config.general.safety.revive_location=Revive location +config.general.safety.revive_location.desc=Where to revive the ship +config.general.safety.revive_location.list.base=Base +config.general.safety.revive_location.list.portal=Portal +config.general.safety.revive_location.list.spot=Spot +config.general.safety.wait_before_revive=Wait before revive (sec) +config.general.safety.wait_before_revive.desc=Seconds to wait before reviving +config.general.safety.wait_after_revive=Wait after revive (sec) +config.general.safety.wait_after_revive.des=Seconds to wait after reviving, lets ship repair +config.general.running=Running +config.general.running.run_from_enemies=Run from enemies +config.general.running.run_from_enemies.desc=If bot should run from enemies who attack you, or have attacked you recently +config.general.running.remember_enemies_for=Remember enemies for +config.general.running.remember_enemies_for.desc=How long to run in sight from enemies that shot you +config.general.running.run_from_enemies_sight=Run from enemies in sight +config.general.running.run_from_enemies_sight.desc=Run from enemies as soon as they enter your minimap +config.general.running.stop_running_no_sight=Stop running when out of sight +config.general.running.stop_running_no_sight.desc=Will stop running if the enemy isn't attacking and is no longer on sight +config.general.running.max_sight_distance=Max sight distance +config.general.running.max_sight_distance.desc=No longer consider enemies in sight if further away than this +config.general.running.ship_ability=Ship ability +config.general.running.ship_ability.desc=Clicked when running away from an enemy that shot you +config.general.running.ship_ability_min=Ship ability min distance +config.general.running.ship_ability_min.desc=Minimum distance to safety to use ability +config.general.running.run_furthest_port=Closest port distance max +config.general.running.run_furthest_port.desc=Run to port further away from enemy, unless port dist under max +config.general.roaming=Roaming & Preferred area behaviour +config.general.roaming.keep=Keep roaming towards same point until reached +config.general.roaming.sequential=Roam to preferred zones in order +config.general.roaming.only_kill_preferred=Only kill NPCs in preferred area +config.general.roaming.only_kill_preferred.desc=Only select and kill NPCs when they are inside a preferred area + +config.collect=Collect +config.collect.stay_away_from_enemies=Stay away from enemies +config.collect.auto_cloack=Auto cloack +config.collect.auto_cloack_key=Auto cloack key +config.collect.radius=Collect while killing radius +config.collect.radius.desc=Resource collection radius while killing NPCs + +config.collect.box_table.resource=Resource +config.collect.box_table.resource.desc=Boxes or other resources the bot has seen +config.collect.box_table.collect=Collect +config.collect.box_table.collect.desc=If the resource should be collected +config.collect.box_table.wait=Wait (ms) +config.collect.box_table.wait.desc=Time to wait to pick up resource, in milliseconds +config.collect.box_table.priority=Priority +config.collect.box_table.priority.desc=#1 priority will be collected before #2 priority + +config.loot=Npc killer +config.loot.sab=Sab +config.loot.sab.desc=Auto sab npcs to survive longer +config.loot.sab.enabled=Enabled +config.loot.sab.key=Key +config.loot.sab.percent=Ship under +config.loot.sab.npc_amount=NPC min shield +config.loot.rsb=Rsb +config.loot.rsb.desc=Uses RSB ammo on aliens with UR flag +config.loot.rsb.enabled=Enabled +config.loot.rsb.key=Key +config.loot.rsb.ammo_refresh=Rsb cooldown +config.loot.ammo_key=Ammo key +config.loot.ship_ability=Offensive ability key +config.loot.ship_ability_min=Offensive ability min health +config.loot.ship_ability_min.desc=Min NPC health to use ability +config.loot.max_circle_iterations=Smart circling iterations +config.loot.max_circle_iterations.desc=Max moves to look ahead to change circle direction, 0 to disable, recommended 3-5 +config.loot.run_config_in_circle=Run config to chase +config.loot.run_config_in_circle.desc=Use run config to follow escaping npcs +config.loot.group_npcs=Group similar NPCs +config.loot.group_npcs.desc=Group NPCs in the same GG in the NPC table +config.loot.npc_distance_ignore=Ignore npcs further than + +config.loot.npc_table.name=Name +config.loot.npc_table.name.desc=Npc names, npcs ending in * are grouped +config.loot.npc_table.radius=Radius +config.loot.npc_table.radius.desc=Distance to keep from this npc +config.loot.npc_table.priority=Priority +config.loot.npc_table.priority.desc=#1 priority will be targeted before #2 priority +config.loot.npc_table.kill=Kill +config.loot.npc_table.kill.desc=If this npc should be killed +config.loot.npc_table.attack_key=Ammo key +config.loot.npc_table.attack_key.desc=Special ammo for this npc, if unset, default is used +config.loot.npc_table.attack_formation=Formation +config.loot.npc_table.attack_formation.desc=Special formation for this npc, if unset, formation from attack config is used +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Several extra flags for the npc + +config.loot.npc_table.extra.no_circle=No circle +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=Don't circle the npc, just stay inside the radius +config.loot.npc_table.extra.ignore_ownership=Ignore ownership +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Continue killing the npc even if it has a white lock +config.loot.npc_table.extra.ignore_attacked=Ignore attacked +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Select the npc even if other players are already shooting it +config.loot.npc_table.extra.aggressive_follow=Aggressive follow +config.loot.npc_table.extra.aggressive_follow.short=AF +config.loot.npc_table.extra.aggressive_follow.desc=Get closer to this NPC while it's running away on low HP +config.loot.npc_table.extra.passive=Passive +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Be passive towards this npc, only shoot if npc is shooting you +config.loot.npc_table.extra.attack_second=Attack second +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Only shoot if others are attacking already.
Must also select ignore attacked & ignore ownership +config.loot.npc_table.extra.use_rsb=Use rsb +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Use RSB ammo to attack this npc +config.loot.npc_table.extra.pet_locator=Pet locator +config.loot.npc_table.extra.pet_locator.short=PL +config.loot.npc_table.extra.pet_locator.desc=Use pet locator to find the npc + +config.pet=Pet +config.pet.enabled=Use pet +config.pet.enabled.desc=If disabled, bot will ignore all pet functions +config.pet.module_id=Pet module +config.pet.module.desc=The list will update to your modules if your pet is on + +config.group=Group +config.group.accept_invites=Accept invites +config.group.accept_invites.desc=Make the bot accept invites +config.group.whitelist_tag=Accept invites from +config.group.whitelist_tag.desc=Only accept invites from whitelisted users +config.group.invite_tag=Send invites to +config.group.invite_tag.desc=Send invites to players with this tag +config.group.open_invites=Open invites (Leader) +config.group.open_invites.desc=Allow anyone to invite others +config.group.kick_tag=Kick players (Leader) +config.group.kick_tag.desc=Kick players from the group + +config.miscellaneous=Miscellaneous +config.miscellaneous.refresh_time=Refresh every +config.miscellaneous.refresh_time.desc=Every how many minutes to refresh +config.miscellaneous.pause_for=Pause after refresh +config.miscellaneous.pause_for.desc=How many minutes to pause for after a refresh +config.miscellaneous.reset_refresh=Reset refresh time on pause/start +config.miscellaneous.reset_refresh.desc=If refresh time should be reset or should continue +config.miscellaneous.drone_repair_percentage=Repair Drone Percentage +config.miscellaneous.drone_repair_percentage.desc=Drones are repaired if above this percentage +config.miscellaneous.honor_lost_exact=Prevent pausing on random honor lost +config.miscellaneous.honor_lost_exact.desc=Only pause bot on honor lost if the lost amount is an exact number from a kill +config.miscellaneous.log_chat=Write chat messages to log file +config.miscellaneous.log_deaths=Write death infos to log file + +config.bot_settings=Bot settings +config.bot_settings.locale=Language +config.bot_settings.force_game_language=Force in-game language +config.bot_settings.force_game_language.desc=Set the in-game language to the bot's language +config.bot_settings.spoof_client=Force using DO client +config.bot_settings.spoof_client.desc=Pretend to be using the DO client. Use under YOUR OWN risk. +config.bot_settings.zone_resolution=Zone precision +config.bot_settings.zone_resolution.desc=Amount of map subdivisions when selecting zones +config.bot_settings.map_start_stop=Start/pause clicking map +config.bot_settings.map_start_stop.desc=Left click to start/pause. Right click to move ship. +config.bot_settings.confirm_exit=Confirm exiting +config.bot_settings.confirm_exit.desc=Confirm before exiting the bot +config.bot_settings.min_tick=Minimum tick time +config.bot_settings.dev_stuff=Developer stuff shown +config.bot_settings.dev_stuff.desc=Enabling this WILL make your bot use more cpu. +config.bot_settings.api=Browser/API, restart to apply +config.bot_settings.api.desc=What browser or flash container to run the bot in +config.bot_settings.fully_hide_api=Fully hide API +config.bot_settings.fully_hide_api.desc=Will hide API in a different way. Lowers cpu usage. May not work properly for everyone +config.bot_settings.disable_master_password=Disable master password +config.bot_settings.disable_master_password.desc=Enable before having added any logins, or having an empty master password +config.bot_settings.display=Display +config.bot_settings.display.toggle=Toggle displaying +config.bot_settings.display.toggle.usernames=Player usernames +config.bot_settings.display.toggle.usernames.desc=The usernames of others on the map +config.bot_settings.display.toggle.hero_name=Hero username +config.bot_settings.display.toggle.hero_name.desc=The username of your own ship +config.bot_settings.display.toggle.hp_shield_num=HP & shield numbers +config.bot_settings.display.toggle.zones=Bot zones +config.bot_settings.display.toggle.zones.desc=Preferred, avoided, and safety places +config.bot_settings.display.toggle.stats_area=Stats area +config.bot_settings.display.toggle.group_area=Group members +config.bot_settings.display.toggle.group_names=Group usernames +config.bot_settings.display.toggle.group_names.desc=The usernames of others in your group +config.bot_settings.display.toggle.booster_area=Boosters area +config.bot_settings.display.toggle.sort_boosters=Order boosters +config.bot_settings.display.toggle.sort_boosters.desc=Order boosters based on remaining time +config.bot_settings.display.trail_length=Trail length +config.bot_settings.display.trail_length.desc=Amount of time the trail should be in the map, in seconds +config.bot_settings.display.button_size=GUI button size +config.bot_settings.display.button_size.desc=Change tab in config & resize main window to update + +# Safety places +safety_places.type.portal=Port +safety_places.type.cbs=CBS +safety_places.type.base=Base + +safety_places.run_mode= [All] Use condition +safety_places.run_mode.always=Always +safety_places.run_mode.enemy_flee_only=Fleeing (Enemy on sight) +safety_places.run_mode.repair_only=Repairing (No enemies) +safety_places.run_mode.refresh=Refresh +safety_places.run_mode.never=Never + +safety_places.jump_mode= [Portal] Jump condition +safety_places.jump_mode.never=Never +safety_places.jump_mode.escaping=Escaping (Enemy shooting) +safety_places.jump_mode.fleeing=Fleeing (Enemy on sight) +safety_places.jump_mode.repairing=Repairing +safety_places.jump_mode.always=Always +safety_places.jump_mode.always_other_side=Always (repair on other side) + +safety_places.cbs_mode= [CBS] Use condition +safety_places.cbs_mode.ally=Ally CBS +safety_places.cbs_mode.ally_neutral=Ally or empty + +#Players +players.add_player=Add player +players.add_player.by_name=User by name +players.add_player.by_id=User by id +players.add_player.username=Username: +players.add_player.user_id=User ID: +players.add_player.invalid_id=Invalid user ID +players.add_player.invalid_id.no_number={0} is not a valid number +players.remove_player.title=Are you sure? +players.remove_player.msg=Do you want to delete the selected {0} players? + +players.tag_default.unset=unset +players.tag_default.all=Everyone +players.tag_default.none=No one +players.select_players.warn.title=Select players first +players.select_players.warn.add=Select the players you want to add the tag to +players.select_players.warn.remove=Select the players you want to remove the tag from +players.select_players.warn.remove_player=Select the players you want to remove +players.delete_tag.confirm.title=Are you sure? +players.delete_tag.confirm.msg=Do you want to delete the {0} tag, and remove it from everyone? + +# Plugins +plugins.reload=Reload plugins +plugins.config_button.enabled.desc=Show configuration +plugins.config_button.disabled.desc=Config disabled, Feature not loaded +plugins.config_button.popup=Can''t edit config +plugins.config_button.popup.desc=Config not available for unloaded feature \nEnable the feature and load it, then try again. + +plugins.issues.invalid_json=Invalid plugin.json +plugins.issues.invalid_json.desc=The minimum version {0} is higher than the supported version {1} +plugins.issues.bot_update=Bot update required +plugins.issues.bot_update.desc=This plugin requires {0}, so it can''t run on DarkBot v{1} +plugins.issues.loaded_twice=Already loaded plugin +plugins.issues.loaded_twice.desc=A plugin with the same name has already been loaded +plugins.issues.plugin_update=Plugin may need update +plugins.issues.plugin_update.desc=The plugin is made for {0}, so it may not work on DarkBot v{1} + +plugins.issues.signature.plugin_not_signed=Plugin not signed +plugins.issues.signature.plugin_not_signed.desc=This plugin hasn''t been signed or has an invalid signature +plugins.issues.signature.unknown_signature=Unknown signature +plugins.issues.signature.unknown_signature.desc=This plugin has been signed with an untrusted certificate +plugins.issues.signature.invalid_signature=Invalid signature +plugins.issues.signature.invalid_signature.desc=The plugin has an invalid signature or has been tampered with + +# Main GUI +gui.hamburger_button.home=HOME +gui.hamburger_button.reload=RELOAD +gui.hamburger_button.copy_sid=COPY SID +gui.hamburger_button.discord=DISCORD +gui.config_button=Show/Hide configuration +gui.start_button=Start/Pause bot +gui.visibility_button=Show/Hide browser +gui.pin_button=Always on top +gui.tray_button=Minimize to tray + +gui.exit=Are you sure you want to exit? +gui.exit.desc=Confirm exiting the bot, can be disabled in settings > bot settings +gui.exit.yes_button=Yes +gui.exit.cancel_button=Cancel + +gui.map.info=v{0} - Refresh: {1}/{2} +gui.map.loading=Loading +gui.map.waiting=WAITING {0} +gui.map.running=RUNNING {0} + +# Modules +module.instructions.title=Instructions +module.kill_n_collect.status=Kill: {0} - Collect: {1} + +module.map_travel.status.has_next=Traveling to {0}, next map: {1} +module.map_travel.status.no_next=Traveling to {0} + +module.disconnect.status=Disconnecting ({0}): {1} +module.disconnect.status_stopped=Disconnected ({0}) +module.disconnect.status_paused=Paused ({0}), reconnecting in {1} +module.disconnect.reason.break=taking a break +module.disconnect.reason.honor=lost honor +module.disconnect.reason.draw_fire=Enemy used draw fire + +# Players +gui.players.not_found.title=User not found +gui.players.not_found.by_name=User with name {0} could not be found.\nTry adding the player by user ID instead +gui.players.not_found.by_id=User with id {0} could not be found.\nMake sure it belongs to a player in your server + +# Issues +bot.issue.module_load_failed=Failed to load module {0}, using default +bot.issue.config_load_failed=Failed to load config file.\nThe bot will use the default config and won''t save.\nDelete config.json file (will erase configs) & restart the bot. +bot.issue.feature.failed_to_tick=Failed to tick +bot.issue.feature.failed_to_load=Feature failed to load +bot.issue.feature.failed_to_load.desc={0} couldn''t be registered properly: {1} + +bot.issue.resolution=Change resolution at
your own risk!
If you experience issues,
set default! + +# Verifier +verifier.discord_warn.title=Join the official discord! +verifier.discord_warn.content.1=This bot is free, if you paid for it or watched ads, you were scammed! +verifier.discord_warn.content.2=Make sure you are in the official discord server to get latest updates for free. +verifier.discord_warn.checkbox.dont_show=Don't show this message again +verifier.discord_warn.button.join_discord=Join discord +verifier.discord_warn.button.ignore=Ignore + +verifier.authenticate.error=The bot failed to start. Try restarting your pc. +verifier.authenticate.title=Discord auth +verifier.authenticate.content=Follow the instructions in your browser, don't close this window.\nAuthentication is local and will not send any data to any server.\nIf the page doesn''t load in your browser, try using chrome or disabling antivirus. + +verifier.panel.invalid_auth=Invalid authentication, try restarting the bot +verifier.panel.authenticated.no_server=You have been authenticated as {0}
You don't seem to be on any server. +verifier.panel.authenticated.servers=You have been authenticated as {0}, member of:
    {1}
+verifier.panel.authenticated.join_official=You should join the official server to get updates!
+verifier.panel.authenticated.blacklisted=You are a member of blacklisted servers who are redistributing my work without my consent.
You must leave blacklisted servers before using the bot. Click to update after you''ve left.
+ +# Language changed +language.changed.title=Bot language changed +language.changed.content=The bot is now using a translation to {0} created by {1}.\nIt is recommended that you restart the bot to fully apply the changes. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@popcorn#1012 \ No newline at end of file diff --git a/src/main/resources/lang/strings_es.properties b/src/main/resources/lang/strings_es.properties new file mode 100644 index 00000000..ab8c832e --- /dev/null +++ b/src/main/resources/lang/strings_es.properties @@ -0,0 +1,341 @@ +# Config tabs +tabs.general=General +tabs.general.desc=Configuraciones generales para el bot +tabs.preferred_zones=Zonas preferidas +tabs.preferred_zones.desc=Zonas a las que el bot deambulará aleatoriamente +tabs.avoided_zones=Zonas Evitadas +tabs.avoided_zones.desc=Zonas tratadas como barreras por el bot +tabs.safety_places=Lugares de seguridad +tabs.safety_places.desc=Lugares seguros para correr para reparar o escapar de enemigos +tabs.players=Jugadores +tabs.plugins.desc=Información y configuraciones para complementos instalados + +# Árbol de configuración +# - General. +config.general=General +config.general.current_module=Módulo +config.general.working_map=Mapa de trabajo +config.general.offensive=Configuración ofensiva +config.general.offensive.desc=Usado para matar NPCs +config.general.roam=Configuración de viaje +config.general.roam.desc=Usado para recorrer el mapa, buscando NPCs +config.general.run=Configuración de huida +config.general.run.desc=Usado para correr hacia un lugar seguro o cambiar de mapa +config.general.formation_check=Verificar formación +config.general.formation_check.desc=Verificar formación a cada X segundos, 0 para deshabilitar +# - General + Seguridad. +config.general.safety=Seguridad +config.general.safety.repair_hp_range=Mantener la vida entre +config.general.safety.repair_hp_range.desc=Incluso disparando a un NPC, huir cuando la vida sea menor a X +config.general.safety.repair_hp_no_npc=Reparar mientras viaja y la vida esta bajo +config.general.safety.repair_hp_no_npc.desc=Vida para huir cuando solo está recorriendo el mapa +config.general.safety.repair_to_shield=Reparar hasta que el escudo +config.general.safety.repair_to_shield.desc=Mínimo escudo para reparar +config.general.safety.repair=Configuración de reparar +config.general.safety.repair.desc=Usado para reparar después de que la formacion actual tenga el escudo al maximo +config.general.safety.max_deaths=Límite de muertes +config.general.safety.max_deaths.desc=Límite de muertes, si es alcanzado, el bot se pausará +config.general.safety.revive_location=Lugar para revivir +config.general.safety.revive_location.desc=Dónde revivir la nave +config.general.safety.revive_location.list.base=Base +config.general.safety.revive_location.list.portal=Portal +config.general.safety.revive_location.list.spot=Punto +config.general.safety.wait_before_revive=Esperar antes de revivir (seg) +config.general.safety.wait_before_revive.desc=Segundos para esperar antes de revivir +config.general.safety.wait_after_revive=Esperar después de revivir (seg) +config.general.safety.wait_after_revive.des=Segundos de espera después de revivir, deja la nave reparar +# - General + Correr. +config.general.running=Correr +config.general.running.run_from_enemies=Huir de enemigos +config.general.running.run_from_enemies.desc=Huir de enemigos cuando te atacan, o atacaron recientemente +config.general.running.remember_enemies_for=Recordar enemigos por +config.general.running.remember_enemies_for.desc=Cuanto tiempo recordar enemigos que te han atacado para huir al verlos +config.general.running.run_from_enemies_sight=Huir de enemigos a la vista +config.general.running.run_from_enemies_sight.desc=Huir de enemigos tan pronto como aparezcan en el minimapa +config.general.running.stop_running_no_sight=Parar de huir cuado no estén a la vista +config.general.running.stop_running_no_sight.desc=Parará de huir cuando el enemigo no está atacando y no está a la vista +config.general.running.max_sight_distance=Distancia máxima de visión +config.general.running.max_sight_distance.desc=No considera los enemigos si están mas lejos que X +config.general.running.ship_ability=Habilidad de la nave +config.general.running.ship_ability.desc=Usada para huir de un enemigo que te disparó +config.general.running.ship_ability_min=Distancia mínima para usar la habilidad +config.general.running.ship_ability_min.desc=Distancia mínima hasta la zona segura para usar habilidad +config.general.running.run_furthest_port=Distancia máxima hacia el portal más cercano +config.general.running.run_furthest_port.desc=Correr hacia el portal más lejano del enemigo, a menos que la distancia al portal sea menor que la máxima +# - General + Comportamientos de viaje y area preferida +config.general.roaming=Comportamientos de viaje y area preferida +config.general.roaming.keep=Seguir viajando hacia el mismo punto hasta alcanzarlo +config.general.roaming.sequential=Viajar a las zonas preferidas en orden +config.general.roaming.only_kill_preferred=Matar NPCs sólo en el área preferida +config.general.roaming.only_kill_preferred.desc=Sólamente seleccionar y matar NPCs cuando estén dentro de una área preferida + +# - Recolectar +config.collect=Recoger +config.collect.stay_away_from_enemies=Mantenerse alejado de los enemigos +config.collect.auto_cloack=Auto camuflaje +config.collect.auto_cloack_key=Tecla para la cpu de camuflaje +config.collect.radius=Radio para recoger mientras ataca NPCs +config.collect.radius.desc=Radio para recoger recursos mientras mata NPCs +# - Recolectar + TABLA +config.collect.box_table.resource=Nombre de Recurso +config.collect.box_table.resource.desc=Cajas u otros recursos que el bot ha visto +config.collect.box_table.collect=Recoger +config.collect.box_table.collect.desc=Si el recurso debe ser recolectado +config.collect.box_table.wait=Espera (ms) +config.collect.box_table.wait.desc=Tiempo de espera para recoger el recurso, en milisegundos. +config.collect.box_table.priority=Prioridad +config.collect.box_table.priority.desc= La prioridad de recoleccion -99>-1>0>1>99 + +# - Matar NPCs +config.loot=Matar NPCs +config.loot.ammo_key=Tecla de munición +config.loot.ship_ability=Tecla de habilidad ofensiva +config.loot.ship_ability_min=Mínima vida para usar habilidad +config.loot.ship_ability_min.desc=Mínimo de vida del NPC para usar habilidad +config.loot.max_circle_iterations=Iteraciones circulares inteligentes +config.loot.max_circle_iterations.desc=Número máximo de movimientos para cambiar la dirección del círculo, 0 para deshabilitar, recomendado 3-5 +config.loot.run_config_in_circle=Configuración de correr para perseguir +config.loot.run_config_in_circle.desc=Usar la configuración de correr para perseguir NPCs que huyen +config.loot.group_npcs=Agrupar NPCs semejantes +config.loot.group_npcs.desc=Agrupe NPCs en la misma GG en la tabla de NPCs +config.loot.npc_distance_ignore=Ignorar npcs mas lejanos que +# - Matar NPCs + SAB +config.loot.sab=SAB +config.loot.sab.desc=Usar SAB automáticamente para sobrevivir más tiempo +config.loot.sab.enabled=Activado +config.loot.sab.key=Tecla +config.loot.sab.percent=Nave bajo +config.loot.sab.npc_amount=Escudo mínimo de NPC +# - Matar NPCs + Rsb +config.loot.rsb=Rsb +config.loot.rsb.desc=Utiliza munición RSB en bichos Uber +config.loot.rsb.enabled=Activar +config.loot.rsb.key=Tecla +config.loot.rsb.ammo_refresh=Enfriamiento de Rsb +# - Matar NPCs + TABLA +config.loot.npc_table.name=Nombre +config.loot.npc_table.name.desc=Los nombres de NPC, npcs que terminan en * están agrupados +config.loot.npc_table.radius=Radio +config.loot.npc_table.radius.desc=Distancia a mantener de este npc +config.loot.npc_table.priority=Prioridad +config.loot.npc_table.priority.desc=La prioridad de matar -99>-1>0>1>99 +config.loot.npc_table.kill=Matar +config.loot.npc_table.kill.desc=Martado Si (TIC) este npc se mata +config.loot.npc_table.attack_key=Tecla de munición +config.loot.npc_table.attack_key.desc=Munición especial para este npc, si no está configurado, se usa el predeterminado +config.loot.npc_table.attack_formation=Formación VANT +config.loot.npc_table.attack_formation.desc=Formación especial para este npc, si no está configurado, se utiliza la formación de la configuración de ataque +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Varias opciones adicionales para el npc +# - Matar NPCs + TABLA + EXTRA´S +config.loot.npc_table.extra.no_circle=No rodear +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=No rodea al npc, solo se quédate dentro del radio +config.loot.npc_table.extra.ignore_ownership=Ignorar propiedad +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Continúa matando el npc incluso si tiene propietario +config.loot.npc_table.extra.ignore_attacked=Ignorar hasta ser atacado +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Selecciona el npc incluso si otros jugadores ya lo están disparando +config.loot.npc_table.extra.passive=Pasivo +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Sé pasivo con este npc, solo dispara si npc te está disparando +config.loot.npc_table.extra.attack_second=Segundo ataque +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc= Solo dispara si otros ya están atacando.
Debe también seleccionar Ignorar hasta ser atacado e ignorar propiedad +config.loot.npc_table.extra.use_rsb=Usar rsb +config.loot.npc_table.extra.use_rsb.short=rsb +config.loot.npc_table.extra.use_rsb.desc=Usa munición RSB para atacar este npc +config.loot.npc_table.extra.pet_locator=Localizar con Pet +config.loot.npc_table.extra.pet_locator.short=PL +config.loot.npc_table.extra.pet_locator.desc=Usar el pet para localizar el npc + +# - Pet +config.pet=Pet +config.pet.enabled=Usar pet +config.pet.enabled.desc=Si está desactivado, el bot ignorará todas las funciónes del pet +config.pet.module_id=Módulo del pet +config.pet.module.desc=La lista se actualizará si su PET está encendida + +# - Grupo +config.group=Grupo +config.group.accept_invites=Aceptar invitaciónes +config.group.accept_invites.desc=Haz que el bot acepte invitaciones +config.group.whitelist_tag=Aceptar invitaciones de +config.group.whitelist_tag.desc=Solo acepta invitaciones de usuarios incluidos en la lista blancas +config.group.invite_tag=Enviar invitaciones a +config.group.invite_tag.desc=Enviar invitaciones a jugadores con esta etiqueta +config.group.open_invites=Invitaciones abiertas (Líder) +config.group.open_invites.desc=Permitir que alguien invite a otros +config.group.kick_tag=Expulsar jugadores (Líder) +config.group.kick_tag.desc=Expulsar a jugadores del grupo + +# - Diversos +config.miscellaneous=Diversos +config.miscellaneous.refresh_time=Reiniciar cada +config.miscellaneous.refresh_time.desc=El bot reiniciará la ventana del juego cada X min +config.miscellaneous.pause_for=Pausar después de reiniciar +config.miscellaneous.pause_for.desc=El bot pausara el juego por X min despues de reiniciar +config.miscellaneous.drone_repair_percentage=Porcentaje para reparar drones +config.miscellaneous.drone_repair_percentage.desc=Los drones serán reparados si están por encima de este porcentaje + +# - Ajustes del bot +config.bot_settings=Ajustes del bot +config.bot_settings.locale=Idioma +config.bot_settings.zone_resolution=Precisión de zonas +config.bot_settings.zone_resolution.desc=Cantidad de subdivisiones del mapa cuando selecciones zonas +config.bot_settings.map_start_stop=Iniciar/pausar clicando en el mapa +config.bot_settings.map_start_stop.desc=Clic izquierdo para iniciar/pausar. Clic derecho para mover la nave. +config.bot_settings.confirm_exit=Confirmar salída +config.bot_settings.confirm_exit.desc=Confirmar antes de salir del bot +config.bot_settings.min_tick=Tiempo mínimo de tick +config.bot_settings.dev_stuff=Mostrar cosas de desarrolladores +config.bot_settings.dev_stuff.desc=Habilitar esto hará que tu bot use más CPU. +# - Ajustes del bot + Mostrar +config.bot_settings.display=Mostrar +config.bot_settings.display.show_names=Mostrar nombres de jugadores +config.bot_settings.display.show_names.desc=Mostrar nombres de jugadores en el minimapa +config.bot_settings.display.hide_name=Ocultar nombre +config.bot_settings.display.hide_name.desc=Ocultar nombre del héroe en el mapa +config.bot_settings.display.hide_group_names=Ocultar nombres de grupo +config.bot_settings.display.hide_group_names.desc=Ocultar nombres de miembros del grupo en el mapa +config.bot_settings.display.trail_length=Rastro del recorrido +config.bot_settings.display.trail_length.desc=Cuánto tiempo tarda en desaparecer el rastro el mapa del bot, en seg +config.bot_settings.display.show_zones=Mostrar las zonas en el mapa principal +config.bot_settings.display.show_zones.desc=Activar para mostrar las zonas preferidas/evitadas del mapa +config.bot_settings.display.button_size=Tamaño de los botónes de la interfaz +config.bot_settings.display.button_size.desc=Cambiar pestaña en la ventana principal de configuración y redimensionar tamaño para actualizar + +# Lugares de seguridad +# - Lugares +safety_places.type.portal=Port +safety_places.type.cbs=CBS +safety_places.type.base=Base +# - [Generico] +safety_places.run_mode= [Todo] Condición de uso +safety_places.run_mode.always=Siempre +safety_places.run_mode.enemy_flee_only=Huyendo (Enemigo a la vista) +safety_places.run_mode.repair_only=Reparación (sin enemigos) +safety_places.run_mode.refresh=Actualizar +safety_places.run_mode.never=Nunca +# - [Portal] +safety_places.jump_mode= [Portal] Condición de salto +safety_places.jump_mode.never=Nunca +safety_places.jump_mode.escaping=Escapar (disparo enemigo) +safety_places.jump_mode.fleeing=Huyendo (Enemigo a la vista) +safety_places.jump_mode.repairing=Reparación +safety_places.jump_mode.always=Siempre +safety_places.jump_mode.always_other_side=Siempre (reparar en el otro lado) +# - [CBS] +safety_places.cbs_mode= [CBS] Condición de uso +safety_places.cbs_mode.ally=Aliado CBS +safety_places.cbs_mode.ally_neutral=Aliado o vacío + +#Jugadores +#Jugadores - Añadir Jugador +players.add_player=Añadir Jugador +players.add_player.by_name=Nombre +players.add_player.by_id=ID +players.add_player.username=NOMBRE: +players.add_player.user_id=ID Jugador: +players.add_player.invalid_id=Invalido la ID +players.add_player.invalid_id.no_number={0} No es un número válido +#Jugadores - tag +players.tag_default.unset=unset +players.tag_default.all=todo el mundo +players.tag_default.none=No one +players.select_players.warn.title=Seleccionar jugadores primero +players.select_players.warn.add=Seleccione los jugadores a los que desea agregar la etiqueta +players.select_players.warn.remove=Selecciona los jugadores de los que quieres eliminar la etiqueta +players.delete_tag.confirm.title=Estas seguro? +players.delete_tag.confirm.msg=¿Desea eliminar la etiqueta {0} y eliminarla de todos? + +# Plugins +plugins.reload=Recargar plugins +plugins.config_button.enabled.desc=Mostrar configuración +plugins.config_button.disabled.desc=Configuración deshabilitada, característica no cargada +plugins.config_button.popup= No se puede editar la configuración +plugins.config_button.popup.desc=La configuración no está disponible para la característica no cargada \nHabilita la característica y cárguela, luego intente nuevamente. +plugins.issues.invalid_json=No válido plugin.json +plugins.issues.invalid_json.desc=La versión mínima {0} es más alta que la versión compatible {1} +plugins.issues.bot_update=Requiere actualización de bot +plugins.issues.bot_update.desc=Este complemento requiere {0}, por lo que no puede ejecutarse en DarkBot v{1} +plugins.issues.plugin_update=El complemento puede necesitar actualización +plugins.issues.plugin_update.desc=El complemento está hecho para {0}, por lo que puede no funcionar en DarkBot v{1} +plugins.issues.signature.plugin_not_signed=Plugin no firmado +plugins.issues.signature.plugin_not_signed.desc=Este complemento no ha sido firmado o tiene una firma no válida +plugins.issues.signature.unknown_signature=Firma desconocida +plugins.issues.signature.unknown_signature.desc=Este complemento ha sido firmado con un certificado no confiable +plugins.issues.signature.invalid_signature=Firma inválida +plugins.issues.signature.invalid_signature.desc=El complemento tiene una firma no válida o ha sido alterado + +# Menu GUI +# Menu GUI - menu +gui.hamburger_button.home=INICIO +gui.hamburger_button.reload=RECARGAR +gui.hamburger_button.copy_sid=COPIAR SID +gui.hamburger_button.discord=DISCORD +# Menu GUI - botones +gui.config_button=Mostrar / Ocultar configuración +gui.start_button=Iniciar / Pausar bot +gui.visibility_button=Mostrar / Ocultar navegador +gui.pin_button=Siempre mostrar por delante +# Menu GUI - Exit +gui.exit=¿Seguro que quieres salir? +gui.exit.desc=Confirme la salida del bot, se puede deshabilitar en configuración> configuración del bot +gui.exit.yes_button=SI +gui.exit.cancel_button=NO +# Menu GUI - map +gui.map.info=v{0} - Refresh: {1}/{2} +gui.map.loading=Cargando +gui.map.waiting=WAITING {0} +gui.map.running=RUNNING {0} + +# Modulos de estado +module.instructions.title=Instrucciones +# - Modulos de estado + Estadistica +module.kill_n_collect.status=Matado: {0} - Recolectado: {1} +# - Modulos de estado + Rumbo +module.map_travel.status.has_next=Viajando a {0}, siguiente mapa: {1} +module.map_travel.status.no_next=Viajando a {0} +# - Modulos de estado + Conexion +module.disconnect.status=Desconectando ({0}): {1} +module.disconnect.status_stopped=Desconectado ({0}) +module.disconnect.status_paused=En pausa ({0}), reconectando en {1} +module.disconnect.reason.break=tomando un descanso +module.disconnect.reason.honor=lost honor +module.disconnect.reason.draw_fire=Enemy used draw fire + +# Cuestiones +bot.issue.module_load_failed=Error al cargar el módulo {0}, usando el predeterminado +bot.issue.config_load_failed=Error al cargar el archivo de configuración. \nEl bot usará la configuración predeterminada y no guardará. \nEliminar el archivo config.json (borrará las configuraciones) y reiniciará el bot. +bot.issue.feature.failed_to_tick=Error al marcar +bot.issue.feature.failed_to_load=Error al cargar la función +bot.issue.feature.failed_to_load.desc={0} no se pudo registrar correctamente: {1} + +# Verificador +# - Verificador + discord +verifier.discord_warn.title=¡Únete al discord oficial! +verifier.discord_warn.content.1=Este bot es gratis, si lo pagó o vio anuncios, ¡fue estafado! +verifier.discord_warn.content.2=Asegúrese de estar en el servidor oficial de discord para obtener las últimas actualizaciones de forma gratuita. +verifier.discord_warn.checkbox.dont_show=No mostrar este mensaje nuevamente +verifier.discord_warn.button.join_discord=Unirse a discord +verifier.discord_warn.button.ignore=Ignorar +# - Verificador + authenticate +verifier.authenticate.error=El bot no pudo iniciarse. Intenta reiniciar tu PC. +verifier.authenticate.title=Autenticación de Discord +verifier.authenticate.content=Siga las instrucciones en su navegador, no cierre esta ventana. \nLa autenticación es local y no enviará ningún dato a ningún servidor. \nSi la página no se carga en su navegador, intente usar Chrome o deshabilitar antivirus. +# - Verificador + panel +verifier.panel.invalid_auth=Autenticación no válida, intente reiniciar el bot +verifier.panel.authenticated.no_server=Ha sido autenticado como {0}
Parece que no está en ningún servidor. +verifier.panel.authenticated.servers=Ha sido autenticado como {0}, miembro de:
    {1}
+verifier.panel.authenticated.join_official=¡Deberías unirte al servidor oficial para obtener actualizaciones!
+verifier.panel.authenticated.blacklisted=Usted es miembro de servidores en la lista negra que están redistribuyendo mi trabajo sin mi consentimiento.
Debe dejar los servidores en la lista negra antes de usar el bot. Haga clic para actualizar después de que se haya ido.
+ +# Mensajes de cambio de idioma +language.changed.title=Idioma del bot cambiado +language.changed.content=El bot ahora está utilizando una traducción a {0} creada por {1}. \nSe recomienda que reinicie el bot para aplicar completamente los cambios. + + + +translation.credit=@Mob Psycho#8973, @kingusoop#4152 & @FuryNocturn#2255 \ No newline at end of file diff --git a/src/main/resources/lang/strings_fr.properties b/src/main/resources/lang/strings_fr.properties new file mode 100644 index 00000000..410b3068 --- /dev/null +++ b/src/main/resources/lang/strings_fr.properties @@ -0,0 +1,308 @@ +# Config tabs +tabs.general=Général +tabs.general.desc=Paramètres général du bot +tabs.preferred_zones=Zones préférées +tabs.preferred_zones.desc=Zones où le bot ira au hasard +tabs.avoided_zones=Zones à éviter +tabs.avoided_zones.desc=Zones traités comme des barrières par le bot +tabs.safety_places=Portail et Sécurité +tabs.safety_places.desc=Endroit sûr pour aller réparer ou fuir l'ennemi +tabs.plugins.desc=Infos et configs pour les plugins installés +tabs.players=Joueurs + + +# Config tree +config.general=Général +config.general.current_module=Module +config.general.working_map=Map en cours +config.general.offensive=Config offensive +config.general.offensive.desc=Utilisé pour tuer les NPC +config.general.roam=Config errer +config.general.roam.desc=Utilisé pour parcourir le map, à la recherche de NPC +config.general.run=Config fuite +config.general.run.desc=Utilisé pour se déplacer en zone sécurisées ou se déplacer entres les cartes +config.general.formation_check=Vérif formation +config.general.formation_check.desc=Vérifie la formation toutes les X secondes, mettez 0 pour la désactiver +config.general.safety=Sécurité +config.general.safety.repair_hp_range=Garde les points de vie entre +config.general.safety.repair_hp_range.desc=Même si en combat avec un npc, fuira en dessous de ce nombre de points de vie +config.general.safety.repair_hp_no_npc=Réparation si le vaisseau erre ou PV inférieur à +config.general.safety.repair_hp_no_npc.desc=Nombre de points de vie avant de fuir lorsque le vaisseau erre +config.general.safety.repair_to_shield=Bouclier mini avant d'arrêter la réparation +config.general.safety.repair_to_shield.desc=Se répare jusqu'à ce que le bouclier indiqué soit atteint +config.general.safety.repair=Config de réparation +config.general.safety.repair.desc=Utilisé pour réparer après que les boucliers de formation soient pleins +config.general.safety.max_deaths=Morts maximums +config.general.safety.max_deaths.desc=Nombre maximum de mort, si atteint, le bot se mettera en pause +config.general.safety.revive_location=Endroit de réapparition +config.general.safety.revive_location.desc=Endroit où le vaisseau réapparaîtra +config.general.safety.revive_location.list.base=Base +config.general.safety.revive_location.list.portal=Portail +config.general.safety.revive_location.list.spot=Sur place +config.general.safety.wait_before_revive=Attendre avant réapparition (en sec) +config.general.safety.wait_before_revive.desc=Nombre de secondes avant que le vaisseau réapparaît +config.general.safety.wait_after_revive=Attendre après réapparition (en sec) +config.general.safety.wait_after_revive.des=Nombre de secondes à attendre après la réapartion (le temps de réparer le vaisseau) +config.general.running=Fuite +config.general.running.run_from_enemies=Fuis les ennemis +config.general.running.run_from_enemies.desc=Si le bot doit fuir les ennemis qui vous attaquent, ou vous ont attaqué récemment +config.general.running.remember_enemies_for=Se souvenir des ennemis +config.general.running.remember_enemies_for.desc=Combien de temps à fuir en vue des ennemis qui vous ont tiré dessus +config.general.running.run_from_enemies_sight=Fuir en vue d'ennemis +config.general.running.run_from_enemies_sight.desc=Fuir les ennemis à partir du moment où ils apparaissent sur la map +config.general.running.stop_running_no_sight=Arrêtez la fuite si ennemi hors vue +config.general.running.stop_running_no_sight.desc=S'arrêtera de fuir si l'ennemi ne tire plus ou s'il n'est plus en vue +config.general.running.max_sight_distance=Distance maximum de vue +config.general.running.max_sight_distance.desc=Distance à laquelle les ennemis ne sont plus considerés comme dans la zone de vue +config.general.running.ship_ability=Compétence de vaisseau +config.general.running.ship_ability.desc=Activée lorsque le bot fuit un ennemi qui vous tire dessus +config.general.running.ship_ability_min=Distance mini pour compétence +config.general.running.ship_ability_min.desc=La distance requise minimum afin que la compétence puisse s'activer +config.general.running.run_furthest_port=Distance max fuite au portail +config.general.running.run_furthest_port.desc=Fuis jusqu'au portail le plus éloigné d'un ennemi, excepté si la distance du portail ne dépasse pas le nombre indiqué +config.general.roaming.keep=Continue d'errer au même point jusqu'à atteint +config.general.roaming=Comportement de l'errance et des zones préférées +config.general.roaming.sequential=Errer aux zones préférées dans l'ordre +config.general.roaming.only_kill_preferred=Tuer NPC dans les zones préférées +config.general.roaming.only_kill_preferred.desc=Sélectionner et tuer uniquement les NPC lorsqu'ils sont à l'intérieur d'une zone préférées + +config.collect=Collecte +config.collect.stay_away_from_enemies=Restez à distance des ennemis +config.collect.auto_cloack=Camouflage automatique +config.collect.auto_cloack_key=Touche de camouflage automatique +config.collect.radius=Rayon de collecte en tuant NPC +config.collect.radius.desc=Distance à laquelle les ressources seront ramassées si le bot combat un NPC + +config.collect.box_table.resource=Ressource +config.collect.box_table.resource.desc=Boîtes ou autres ressources que le bot a vues +config.collect.box_table.collect=Collecte +config.collect.box_table.collect.desc=Si la ressource doit être collectée +config.collect.box_table.wait=Wait (ms) +config.collect.box_table.wait.desc=Temps d'attente pour collecter la ressource, en millisecondes +config.collect.box_table.priority=Priorité +config.collect.box_table.priority.desc=#1 la priorité sera collecte avant #2 priorité + +config.loot=Tueur de NPC +config.loot.sab=SAB +config.loot.sab.desc=Sab automatique pour survivre plus longtemps +config.loot.sab.enabled=Activé +config.loot.sab.key=Touche +config.loot.sab.percent=Vaisseau sous +config.loot.sab.npc_amount=Bouclier minimum du NPC +config.loot.rsb=RSB +config.loot.rsb.desc=Utilisera les munitions RSB sur les NPC avec l'extra "Utilisé RSB" d'activé +config.loot.rsb.enabled=Activé +config.loot.rsb.key=Touche +config.loot.rsb.ammo_refresh=RSB délai +config.loot.ammo_key=Touche de munition +config.loot.ship_ability=Touche compétence offensive +config.loot.ship_ability_min=Compétence offensive min HP +config.loot.ship_ability_min.desc=Points de vies minimum du NPC pour l'activation de la compétence offensive +config.loot.max_circle_iterations=Itération circulaire intelligente +config.loot.max_circle_iterations.desc=Mouvements maximum afin de changer de direction, 0 pour désactiver, 3-5 recommandé +config.loot.run_config_in_circle=Config de fuite pour chasser +config.loot.run_config_in_circle.desc=Utilise la config de fuite afin de suivre les NPC qui s'échappent +config.loot.group_npcs=Regroupe NPC similaires +config.loot.group_npcs.desc=Regroupe les mêmes NPC d'une GG dans la même case +config.loot.npc_distance_ignore=Ignore NPC plus loin que + +config.loot.npc_table.name=Nom +config.loot.npc_table.name.desc=Les noms des NPC, les npc se terminant par * sont regroupés +config.loot.npc_table.radius=Rayon +config.loot.npc_table.radius.desc=Distance à garder de ce NPC +config.loot.npc_table.priority=Priorité +config.loot.npc_table.priority.desc=#1 la priorité sera collecte avant #2 priorité +config.loot.npc_table.kill=Tuer +config.loot.npc_table.kill.desc=Si ce NPC devait être tuer +config.loot.npc_table.attack_key=Touche de munitions +config.loot.npc_table.attack_key.desc=Munitions spéciales pour ce NPC, si non définies, la valeur par défaut est utilisée +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Plusieurs options en plus pour les NPC + +config.loot.npc_table.extra.no_circle=Pas tourner le NPC +config.loot.npc_table.extra.no_circle.short=TNPC +config.loot.npc_table.extra.no_circle.desc=Ne tourne pas autour du NPC, reste simplement dans le rayon +config.loot.npc_table.extra.ignore_ownership=Ignorer la propriété +config.loot.npc_table.extra.ignore_ownership.short=IP +config.loot.npc_table.extra.ignore_ownership.desc=Continue à attaquer le NPC même s'il a un lock blanc +config.loot.npc_table.extra.ignore_attacked=Ignorer l'attaquant +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Sélectionne le NPC même si d'autres joueurs l'attaque déjà +config.loot.npc_table.extra.passive=Passive +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Soyez passif envers ce NPC, ne tire que si le NPC vous tire dessus +config.loot.npc_table.extra.attack_second=Attaque seconde +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Attaque le NPC seulement si d'autres joueurs l'attaques déjà.
D'abord sélectionner l'option "Ignorer l'attaquant et "Ignorer la propriété. +config.loot.npc_table.extra.use_rsb=Utiliser RSB +config.loot.npc_table.extra.use_rsb.short=URSB +config.loot.npc_table.extra.use_rsb.desc=Utilise les munitions RSB pour attaquer cet NPC +config.loot.npc_table.extra.pet_locator=Détection NPC REX +config.loot.npc_table.extra.pet_locator.short=DNPC +config.loot.npc_table.extra.pet_locator.desc=Utilise le module de détection d'ennemi du REX + +config.pet=R.E.X +config.pet.enabled=Utiliser le REX +config.pet.enabled.desc=Si désactivée, le bot ignorera toutes les options utilisées +config.pet.module_id=Module de REX +config.pet.module.desc=Cette liste sera actualisé en fonction des modules de rex utilisés + +config.group=Groupe +config.group.accept_invites=Accepter les invites +config.group.accept_invites.desc=Le bot acceptera les invitations de groupe +config.group.whitelist_tag=Accepter les invites de +config.group.whitelist_tag.desc=Accepter seulement les invitations de votre whitelist (Onglet "Joueurs") +config.group.invite_tag=Envoyer une invite à +config.group.invite_tag.desc=Envoyer une invitation à un joueur avec ce tag +config.group.open_invites=Activer les invites (Chef) +config.group.open_invites.desc=Active le droit d'invitation de groupe pour tout le monde +config.group.kick_tag=Kick joueur (Chef) +config.group.kick_tag.desc=Kick le joueur du groupe + +config.miscellaneous=Divers +config.miscellaneous.refresh_time=Refresh toutes les +config.miscellaneous.refresh_time.desc=Toutes les X minutes avant de refresh le jeu +config.miscellaneous.pause_for=Pause après refresh +config.miscellaneous.pause_for.desc=Combien de minutes de pause après le refresh +config.miscellaneous.drone_repair_percentage=Pourcent réparation drones +config.miscellaneous.drone_repair_percentage.desc=Les drones seront réparés si les dégats sont supérieurs à ce pourcentage + +config.bot_settings=Paramètres du bot +config.bot_settings.locale=Langue +config.bot_settings.zone_resolution=Précision de zone +config.bot_settings.zone_resolution.desc=Nombre de sous divisions de la carte lors de la sélection de zones +config.bot_settings.map_start_stop=Start/pause clicking map +config.bot_settings.map_start_stop.desc=Clique gauche pour démarrer/pause. Clique droit pour déplacer le vaisseau +config.bot_settings.confirm_exit=Confirmation de fermeture +config.bot_settings.confirm_exit.desc=Message de confirmation lors de la fermeture de darkbot +config.bot_settings.min_tick=Temps de tick minimum +config.bot_settings.dev_stuff=Outils devs visibles +config.bot_settings.dev_stuff.desc=Activer cette option fera que le bot utilisera plus de votre cpu +config.bot_settings.display=Afficher +config.bot_settings.display.show_names=Afficher les pseudos +config.bot_settings.display.show_names.desc=Affichera les pseudonymes sur la minicarte +config.bot_settings.display.hide_name=Cacher mon pseudo +config.bot_settings.display.hide_name.desc=Cache le pseudo de mon compte sur la carte +config.bot_settings.display.hide_group_names=Cacher pseudo groupe +config.bot_settings.display.hide_group_names.desc=Cacher les pseudos du groupe sur la map +config.bot_settings.display.trail_length=Distance du tracé +config.bot_settings.display.trail_length.desc=Temps que le tracé suivant le vaisseau s'affichera sur la carte, en secondes +config.bot_settings.display.show_zones=Zones sur la carte +config.bot_settings.display.show_zones.desc=Activer pour montrer les zones préférées/évitées dans la carte principale +config.bot_settings.display.button_size=Taille des boutons du GUI +config.bot_settings.display.button_size.desc=Modifier l'onglet dans la fenêtre principale de configuration & redimension pour mettre à jour + +# Safety places +safety_places.type.portal=Portail +safety_places.type.cbs=CBS +safety_places.type.base=Base + +safety_places.run_mode= [Tout] Condition d'utilisation +safety_places.run_mode.always=Toujours +safety_places.run_mode.enemy_flee_only=Fuir (Fuir en vue d'ennemis) +safety_places.run_mode.repair_only=Réparer (Pas d'ennemis) +safety_places.run_mode.refresh=Rafraîchir +safety_places.run_mode.never=Jamais + +safety_places.jump_mode= [Portail] Condition de saut +safety_places.jump_mode.never=Jamais +safety_places.jump_mode.escaping=Fuis les ennemis (Ennemis qui vous ont tiré dessus) +safety_places.jump_mode.fleeing=Fuis (En vue d'ennemis) +safety_places.jump_mode.repairing=Réparer +safety_places.jump_mode.always=Toujours +safety_places.jump_mode.always_other_side=Toujours (réparation de l'autre côté) + +safety_places.cbs_mode= [CBS] Condition d'utilisation +safety_places.cbs_mode.ally=Allié CBS +safety_places.cbs_mode.ally_neutral=Allié ou Vide + +# Plugins +plugins.reload=Recharger plugins +plugins.config_button.enabled.desc=Afficher la configuration +plugins.config_button.disabled.desc=Configuration désactivée, fonctionnalité non chargée +plugins.config_button.popup=Impossible de modifier la configuration +plugins.config_button.popup.desc=Configuration non disponible pour la fonction non chargée\nActivez la fonction et chargez-la, puis réessayez. + +plugins.issues.invalid_json=Invalide plugin.json +plugins.issues.invalid_json.desc=La version minimale {0} est supérieur à la version prise en charge {1} +plugins.issues.bot_update=Mise à jour du bot obligatoire +plugins.issues.bot_update.desc=Ce plugin nécessite {0}, donc il ne peut pas fonctionner sur DarkBot v{1} +plugins.issues.plugin_update=Plugin nécessitant une mise à jour +plugins.issues.plugin_update.desc=Ce plugin est fait pour {0}, il ne peut donc pas fonctionner sur DarkBot v{1} + +plugins.issues.signature.plugin_not_signed=Plugin non signé +plugins.issues.signature.plugin_not_signed.desc=Ce plugin n'a pas été signé ou a une signature invalide +plugins.issues.signature.unknown_signature=Signature inconnue +plugins.issues.signature.unknown_signature.desc=Ce plugin a été signé avec un certificat non approuvé +plugins.issues.signature.invalid_signature=Signature non valide +plugins.issues.signature.invalid_signature.desc=Le plugin a une signature invalide ou a été falsifié + +# Main GUI +gui.hamburger_button.home=PAGE D'ACCUEIL +gui.hamburger_button.reload=ACTUALISER +gui.hamburger_button.copy_sid=COPIER LE SID +gui.hamburger_button.discord=DISCORD +gui.config_button=Afficher/Masquer la configuration +gui.start_button=Démarrer/Pause bot +gui.visibility_button=Afficher/Masquer le navigateur +gui.pin_button=Toujours devant l'écran + +gui.exit=Êtes-vous sûr de vouloir quitter? +gui.exit.desc=Confirmer la sortie du bot, peut être désactivé dans les paramètres > paramètres du bot +gui.exit.yes_button=Oui +gui.exit.cancel_button=Annuler +gui.map.loading=Chargement + +#Players +players.add_player=Ajouter un joueur +players.add_player.by_name=Joueur par nom +players.add_player.by_id=Joueur par ID +players.add_player.username=Pseudo: +players.add_player.user_id=ID joueur: +players.add_player.invalid_id=ID joueur invalide +players.add_player.invalid_id.no_number={0} n'est pas un nombre valide + +# Modules +module.instructions.title=Instructions +module.kill_n_collect.status=Tuer: {0} - Collecte: {1} + +module.map_travel.status.has_next=Voyager à {0}, carte suivante: {1} +module.map_travel.status.no_next=Voyager à {0} + +module.disconnect.status=Déconnexion({0}): {1} +module.disconnect.status_stopped=Déconnecté ({0}) +module.disconnect.status_paused=Pause ({0}), se reconnecte {1} +module.disconnect.reason.break=Faire une pause +module.disconnect.reason.honor=Honneur perdu + +# Problèmes +bot.issue.module_load_failed=Impossible de charger le module{0}, en utilisant par défaut +bot.issue.config_load_failed=Impossible de charger le fichier de configuration.\nLe bot utilisera la configuration par défaut et ne enregistrera pas.\nDelete config.json file (effacera les configurations) & redémarrez le bot. +bot.issue.feature.failed_to_tick=Impossible de cocher +bot.issue.feature.failed_to_load=Impossible de charger la fonctionnalité +bot.issue.feature.failed_to_load.desc={0} n'a pas pu être enregistré correctement:: {1} + +# Vérifieur +verifier.discord_warn.title=Rejoignez la discord officielle! +verifier.discord_warn.content.1=Ce bot est gratuit, si vous l'avez payé ou regardé des publicités, vous avez été victime d'une arnaque! +verifier.discord_warn.content.2=Assurez-vous que vous êtes sur le serveur Discord officiel pour obtenir gratuitement les dernières mises à jour. +verifier.discord_warn.checkbox.dont_show=N'afficher plus ce message +verifier.discord_warn.button.join_discord=Rejoignez Discord +verifier.discord_warn.button.ignore=Ignorer + +verifier.authenticate.error=Le bot n'a pas pu démarrer. Essayez de redémarrer votre PC. +verifier.authenticate.title=Discord auth +verifier.authenticate.content=Suivez les instructions de votre browser, ne fermez pas cette fenêtre. \n L'authentification est locale et n'enverra aucune donnée à aucun serveur. \n Si la page ne se charge pas dans votre navigateur, essayez d'utiliser Chrome ou de désactiver l'antivirus. + +verifier.panel.invalid_auth=Authentification non valide, essayez de redémarrer le bot +verifier.panel.authenticated.no_server=Vous avez été authentifié en tant que {0}
Vous ne semblez pas être sur le serveur +verifier.panel.authenticated.servers=Vous avez été authentifié en tant que {0}, membre de:
    {1}
+verifier.panel.authenticated.join_official=Vous devriezrejoignez le serveur officiel pour obtenir des mises à jour!
+verifier.panel.authenticated.blacklisted=Vous êtes membre de serveurs sur liste noire qui redistribuent mon travail sans mon consentement.
Vous devez quitter les serveurs sur liste noire avant d'utiliser le bot. Cliquez pour mettre à jour après votre départ
+ +# La langue a changé +language.changed.title=La langue du Bot a changé +language.changed.content=Le bot utilise maintenant une traduction de {0} créé par{1}.\n Il est recommandé de redémarrer le bot pour appliquer pleinement les modifications. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@Aikio#6409, @Naviii#9426 & Wide#6464 diff --git a/src/main/resources/lang/strings_hu.properties b/src/main/resources/lang/strings_hu.properties new file mode 100644 index 00000000..6cfa8d11 --- /dev/null +++ b/src/main/resources/lang/strings_hu.properties @@ -0,0 +1,324 @@ +# Config tabs +tabs.general=Általános +tabs.general.desc=Általános beállítások +tabs.preferred_zones=Preferált zónák +tabs.preferred_zones.desc=Ezeken a zónákon fog a BOT mozogni +tabs.avoided_zones=Kikerülendő zónák +tabs.avoided_zones.desc=A bot által akadályként kezelt zónák +tabs.safety_places=Biztonságos helyek +tabs.safety_places.desc=Biztonságos hely ahová a BOT menjen ha javít vagy menekül +tabs.players=Játékosok +tabs.plugins.desc=Információ és konfiguráció a telepített Pluginokhoz + +# Config tree +config.general=Általános +config.general.current_module=Választott Modul +config.general.working_map=Választott térkép +config.general.offensive=Támadó konfiguráció +config.general.offensive.desc=A BOT ezt a konfigot és formációt használja TÁMADÁS közben +config.general.roam=Kereső konfiguráció +config.general.roam.desc=A BOT ezt a konfigot és formációt használja KERESÉS közben +config.general.run=Futó konfiguráció +config.general.run.desc=A BOT ezt a konfigot és formációt használja FUTÁS vagy TÉRKÉP VÁLTÁS közben +config.general.formation_check=Formáció ellenőrzése +config.general.formation_check.desc=Ellenőrzi a formációt X másodpercenként, használj 0-át a kikapcsolásához +config.general.safety=Biztonság +config.general.safety.repair_hp_range=A HP a két érték között tartása +config.general.safety.repair_hp_range.desc=NPC támadás közben a kettő érték között tartja a hajó életerejét, ha eléri az alső értéket elmegy javítani +config.general.safety.repair_hp_no_npc=Keresés közben MINIMUM élet +config.general.safety.repair_hp_no_npc.desc=Keresés közben ha ennyi a hajó életereje akkor elmegy javítani +config.general.safety.repair_to_shield=Javítsa a pajzsot eddig +config.general.safety.repair_to_shield.desc=A hajó pajzsának javítása eddig az értékig +config.general.safety.repair=Javító konfiguráció +config.general.safety.repair.desc=Ebben a konfigban és formációban javít, ha a FUTÓ konfiguráció pajzsa már feljavult +config.general.safety.max_deaths=Maximum halál +config.general.safety.max_deaths.desc=Maximum halálok száma, ha eléri ezt a számot, a bot megáll +config.general.safety.revive_location=Hajó javítás helye +config.general.safety.revive_location.desc=Hova javítsa a bot a hajót +config.general.safety.revive_location.list.base=Bázisra +config.general.safety.revive_location.list.portal=Portálhoz +config.general.safety.revive_location.list.spot=Helyben +config.general.safety.wait_before_revive=Várakozás hajójavítás előtt (másodperc) +config.general.safety.wait_before_revive.desc=Ennyi másodpercet vár mielőtt megjavítja a hajót +config.general.safety.wait_after_revive=Várakozás hajójavítás után (másodperc) +config.general.safety.wait_after_revive.des=Ennyi másodpercet vár miután megjavította a hajót +config.general.running=Futás, menekülés +config.general.running.run_from_enemies=Futás ellenségek elől +config.general.running.run_from_enemies.desc=Fusson az ellenségek elől aki megtámad téged, vagy már megtámadott korábban +config.general.running.remember_enemies_for=Jegyezze meg az ellenségeket ekkora távolságban +config.general.running.remember_enemies_for.desc=Milyen messze fusson az ellenségektől a távolságban akik megtámadnak téged +config.general.running.run_from_enemies_sight=Fusson az ellenségek elől a látóhatárban +config.general.running.run_from_enemies_sight.desc=Meneküljön az ellenség elől amint megjelenik a térképeden +config.general.running.stop_running_no_sight=Hagyja abba a menekülést a látóhatáron kívül +config.general.running.stop_running_no_sight.desc=Abbahagyja a menekülést ha az ellenség nem lő és nincs látóhatáron belül +config.general.running.max_sight_distance=Maximum látóhatár távolsága +config.general.running.max_sight_distance.desc=Ebben a távolságban ha ellenséget lát akkor menekül +config.general.running.ship_ability=Menekülő képesség +config.general.running.ship_ability.desc=Ha menekülés közben meglő az ellenség akkor megnyomja ezt a gombot +config.general.running.ship_ability_min=Hajó képesség minimum távolsága +config.general.running.ship_ability_min.desc=Minimum távolság a biztonságos zónától, hogy használja a képességet +config.general.running.run_furthest_port=Legközelebbi portál távolsága +config.general.running.run_furthest_port.desc=Fusson a portálhoz az ellenség elől, kivéve ha a távolság a maximum alatt van +config.general.roaming=Keresés & Preferált zóna beállításai +config.general.roaming.keep=Addig menjen a térképen amíg el nem éri a kijelölt pontot +config.general.roaming.sequential=Menjen sorban a PREFERÁLT zónák között +config.general.roaming.only_kill_preferred=Csak a PREFERÁLT ZÓNÁBAN támadjon NPC-ket +config.general.roaming.only_kill_preferred.desc=Csak akkor jelöljön ki és támadjon meg NPC-t ha az aa PREFERÁLT ZÓNÁBAN van + +config.collect=Nyersanyag gyűjtés beállításai +config.collect.stay_away_from_enemies=Maradjon távol az ellenségektől +config.collect.auto_cloack=Automatikus álca +config.collect.auto_cloack_key=Automatikus álca gombja +config.collect.radius=Támadás közbeni nyersanyag gyűjtés távolsága +config.collect.radius.desc=NPC támadás közben ha a nyersanyag ilyen közel van a hajóhoz akkor felszedi + +config.collect.box_table.resource=Nyersanyag +config.collect.box_table.resource.desc=Dobozok vagy más nyersanyagok amiket a BOT látott +config.collect.box_table.collect=Begyüjtés +config.collect.box_table.collect.desc=Használd ha szeretnéd hogy a BOT begyüjtse a Nyersanyagokat +config.collect.box_table.wait=Várás (ms) +config.collect.box_table.wait.desc=Ennyi MILISECUNDUMOT vár a begyűjtés alatt +config.collect.box_table.priority=Fontosság +config.collect.box_table.priority.desc=#1 -es prioritás előbb lesz begyüjtve mint a #2 prioritású + +config.loot=NPC táblázat +config.loot.sab=SAB +config.loot.sab.desc=Automatikus SAB lőszerre váltás a pajzs visszaszerzés érdekében +config.loot.sab.enabled=Bekapcsolva +config.loot.sab.key=Gomb +config.loot.sab.percent=Hajó minimum pajzsa (%) +config.loot.sab.npc_amount=NPC minimum pajzsa +config.loot.rsb=RSB +config.loot.rsb.desc=RSB lőszer használata az NPC-k ellen +config.loot.rsb.enabled=Használat +config.loot.rsb.key=Gomb +config.loot.rsb.ammo_refresh=Rsb töltési idő +config.loot.ammo_key=Lőszer gomb +config.loot.ship_ability=Támadó képesség gomb +config.loot.ship_ability_min=Támadó képesség +config.loot.ship_ability_min.desc=Minimum NPC HP a képesség használatához +config.loot.max_circle_iterations="Okos" körözés +config.loot.max_circle_iterations.desc=Maximum mozgás hogy megváltoztassa a körözés irányát, 0 a kikapcsoláshoz, ajánlott 3-5 +config.loot.run_config_in_circle=Futó konfiguráció NPC üldözéshez +config.loot.run_config_in_circle.desc=Használja a Futó konfigurációt NPC üldözéshez +config.loot.group_npcs=Csoportosítsa a hasonló NPC-ket +config.loot.group_npcs.desc=Csoportosítsa az NPC-ket GG-ben az NPC táblán belül +config.loot.npc_distance_ignore=Hagyja figyelmen kívül az NPC-t e távolság után + +config.loot.npc_table.name=Név +config.loot.npc_table.name.desc=NPC nevek, az NPC nevek amik *-al végződnek csoportosítva vannak +config.loot.npc_table.radius=Távolság +config.loot.npc_table.radius.desc=Távolság tartása az NPC-től +config.loot.npc_table.priority=Prioritás +config.loot.npc_table.priority.desc=#1 prioritású NPC hamarabb lesz kijelölve mint a #2 prioritású +config.loot.npc_table.kill=Támadás +config.loot.npc_table.kill.desc=Ezt az NPC-t támadja a BOT +config.loot.npc_table.attack_key=Lőszer gomb +config.loot.npc_table.attack_key.desc=Speciális lőszer ehhez az NPC-hez, ha nincs beállítva, az alapértelmezett használata +config.loot.npc_table.attack_formation=Formáció +config.loot.npc_table.attack_formation.desc=Speciális formáció ehhez az NPC-hez, ha nincs beállítva, a támadó konfiguráció formációját fogja használni +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Extrák az NPC-hez + +config.loot.npc_table.extra.no_circle=Ne körözzön +config.loot.npc_table.extra.no_circle.short=NK +config.loot.npc_table.extra.no_circle.desc=Ne körözzön az NPC körül, csak maradjon a távolságon belül +config.loot.npc_table.extra.ignore_ownership=Hagyja figyelmen kívül az NPC tulajdonosát +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Folytassa az NPC lövését akkor is ha a célkereszt fehér rajta +config.loot.npc_table.extra.ignore_attacked=Hagyja figyelmen kívül a megtámadott NPC-ket +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Lőjje az NPC-t akkor is ha más már elkezdte +config.loot.npc_table.extra.passive=Passzív +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Legyen passzív ehhez az NPC-hez, csak akkor támadja meg ha az NPC megtámadja a hajót +config.loot.npc_table.extra.attack_second=Támadás másodjára +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Csak akkor támadjon ha valaki már megtámadta.
Kötelező kiválasztani a "Hagyja figyelmen kívül a megtámadott NPC-ket" (IA) és az "Hagyja figyelmen kívül a birtoklót (ownership)" (IO) funkciókat is. +config.loot.npc_table.extra.use_rsb=Rsb használata +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Használjon RSB lőszert az NPC megöléséhez + +config.pet=Pet +config.pet.enabled=Pet használat +config.pet.enabled.desc=Ha ki van kapcsolva, a BOT nem fogja használni a Petet +config.pet.module=A (Pet) modul száma használathoz +config.pet.module.desc=0 - passzív
1 - Védelmi mód
2 - így tovább + +config.group=Csoport +config.group.accept_invites=Meghívás elfogadása +config.group.accept_invites.desc=Engedélyezi a Bot számára hogy elfogadja a csoport meghívókat +config.group.whitelist_invites=Kizárólag fehérlistás meghívók +config.group.whitelist_invites.desc=Csak a fehérlistás játékosok meghívását fogadja el +config.group.invite_tag=Küldjön meghívót nekik: +config.group.invite_tag.desc=Küldjön a BOT meghívót, a játékosoknak ezzel a Tag-el. +config.group.open_invites=Meghívók megnyitása (Vezető) +config.group.open_invites.desc=Engedélyezés mindenki számára, hogy meghívjon másokat +config.group.kick_tag=Játékosok kirúgása (Vezető) +config.group.kick_tag.desc=Játékosok kirúgása a csoportból. + +config.miscellaneous=Egyéb +config.miscellaneous.refresh_time=Frissítés (perc) +config.miscellaneous.refresh_time.desc=Hány percenként frissítse a játékablakot (lag megakadályozása érdekében) +config.miscellaneous.pause_for=Szünet frissítés után +config.miscellaneous.pause_for.desc=Hány percet várjon frissítés után +config.miscellaneous.drone_repair_percentage=Drónok javításának százaléka +config.miscellaneous.drone_repair_percentage.desc=Megjavítja a drónokat e százalék fölött + +config.bot_settings=Bot beállítások +config.bot_settings.lang=Nyelv +config.bot_settings.zone_resolution=Zóna pontosság +config.bot_settings.zone_resolution.desc=A térképen kijelölt zónák nagysága +config.bot_settings.map_start_stop=Elindítás/megállítás a térképen való kattintáskor +config.bot_settings.map_start_stop.desc=Bal kattintás az elindításhoz/megállításhoz. Jobb klikk a hajó mozgatásához +config.bot_settings.confirm_exit=Kilépés megerősítése +config.bot_settings.confirm_exit.desc=Megerősítést kér mielőtt kilép a BOT-ból +config.bot_settings.min_tick=Minimum tick idő +config.bot_settings.dev_stuff=Fejlesztői eszközök mutatása +config.bot_settings.dev_stuff.desc=Ennek használatakor a Bot nagyobb százalékban használja a processzort +config.bot_settings.display=Kijelző +config.bot_settings.display.show_names=Nevek mutatása +config.bot_settings.display.show_names.desc=Nevek mutatása térképen +config.bot_settings.display.hide_name=Név eltüntetése +config.bot_settings.display.hide_name.desc=Hajó nevének eltüntetése a térképen +config.bot_settings.display.hide_group_names=Csoport tagok nevének elrejtése +config.bot_settings.display.hide_group_names.desc=Csoport tagok nevének elrejtése a minitérképen +config.bot_settings.display.trail_length=Vonal hosszúság +config.bot_settings.display.trail_length.desc=Az idő mennyisége amíg látszódik a vonal a térképen, másodpercekben +config.bot_settings.display.show_zones=Zónák mutatása a fő térképen +config.bot_settings.display.show_zones.desc=Használd az előnyben részesített/kikerülendő zónákat a térképen +config.bot_settings.display.button_size=GUI gombok mérete +config.bot_settings.display.button_size.desc=Válts fület a Configban és méretezd a fő ablakot a frissítéshez + +# Safety places +safety_places.type.portal=Portál +safety_places.type.cbs=Klán Bázis Állomás +safety_places.type.base=Bázis + +safety_places.run_mode= [Összes] Használati kondíció +safety_places.run_mode.always=Mindig +safety_places.run_mode.enemy_flee_only=Menekülés (Ellenséget lát) +safety_places.run_mode.repair_only=Javítás (Nincs ellenség) +safety_places.run_mode.refresh=Frissítés +safety_places.run_mode.never=Soha + +safety_places.jump_mode= [Portál] Ugrási kondíció +safety_places.jump_mode.never=Soha +safety_places.jump_mode.escaping=Menekülés (Ellenség meglőtt) +safety_places.jump_mode.fleeing=Menekülés (Ellenséget lát) +safety_places.jump_mode.repairing=Javítás +safety_places.jump_mode.always=Mindig +safety_places.jump_mode.always_other_side=Mindig (javítás a m.o.) + +safety_places.cbs_mode= [K.B.Á.] Használati kondíció +safety_places.cbs_mode.ally=Szövetséges K.B.Á +safety_places.cbs_mode.ally_neutral=Szövetséges vagy üres + +#Players +players.add_player=Játékos hozzáadása +players.add_player.by_name=Játékos név alapján +players.add_player.by_id=Játékos ID alapján +players.add_player.username=Név: +players.add_player.user_id=Játékos ID: +players.add_player.invalid_id=Érvénytelen játékos ID +players.add_player.invalid_id.no_number={0} nem egy valós szám + +players.tag_default.unset=Nincs beállítás +players.tag_default.all=Mindenki +players.tag_default.none=Senki +players.select_players.warn.title=Válaszd ki a játékost először +players.select_players.warn.add=Válaszd ki a játékost akihez a Tag-et hozzá akarod adni +players.select_players.warn.remove=Válaszd ki a játékost akitől a Tag-et el szeretnéd venni +players.delete_tag.confirm.title=Biztos vagy benne? +players.delete_tag.confirm.msg=Ki szeretnéd törölni a {0} Tag-et, és eltávolítani mindenkiről? + +# Plugins +plugins.reload=Pluginok újratöltése +plugins.config_button.enabled.desc=Konfiguráció mutatása +plugins.config_button.disabled.desc=Konfiguráció kikapcsolva, a Funkciót nem sikerült betölteni +plugins.config_button.popup=Konfiguráció szerkesztése sikertelen +plugins.config_button.popup.desc=A konfig nem elérhető betöltetlen funkcióra \nKapcsold be a funkciót, töltsd be és próbáld újra. + +plugins.issues.invalid_json=Érvénytelen plugin.json +plugins.issues.invalid_json.desc=A minimum verzió {0} magasabban van a támogatott verziónál {1} +plugins.issues.bot_update=Bot frissítés szükséges +plugins.issues.bot_update.desc=A plugin követeli a {0} (verziót), szóval nem fog működni a DarkBot v{1} verzióján. +plugins.issues.plugin_update=Előfordulhat, hogy a Plugin frissítésre szorul +plugins.issues.plugin_update.desc=A plugin {0} számára készült, így előfordulhat, hogy nem működik a DarkBot v {1} verzióján. + +plugins.issues.signature.plugin_not_signed=Plugin nincs aláírva +plugins.issues.signature.plugin_not_signed.desc=Ez a Plugin nem lett aláírva, vagy hibás az aláírás +plugins.issues.signature.unknown_signature=Ismeretlen aláírás +plugins.issues.signature.unknown_signature.desc=Ezt a Plugint nem megbízható tanúsítvánnyal írták alá +plugins.issues.signature.invalid_signature=Érvénytelen aláírás +plugins.issues.signature.invalid_signature.desc=A plugin érvénytelen aláírással rendelkezik, vagy hamisították + +# Main GUI +gui.hamburger_button.home=WEBOLDAL +gui.hamburger_button.reload=ÚJRATÖLTÉS +gui.hamburger_button.copy_sid=SID MÁSOLÁSA +gui.hamburger_button.discord=DISCORD +gui.config_button=Konfiguráció Megjelenítése/Elrejtése +gui.start_button=BOT Megállítása/Elindítása +gui.visibility_button=Böngésző Megjelenítése/Elrejtése +gui.pin_button=Mindig felül legyen + +gui.exit=Biztos vagy benne hogy be akarod zárni a BOT-ot? +gui.exit.desc=Erősísd meg a BOT kilépését, ez kikapcsolható a Konfigurációban - Bot beállítások +gui.exit.yes_button=Igen +gui.exit.cancel_button=Nem + +gui.map.info=v{0} - Újratöltés: {1}/{2} +gui.map.loading=Betöltés +gui.map.waiting=VÁRAKOZÁS {0} +gui.map.running=FUTÁS {0} + +# Modules +module.instructions.title=Instrukciók +module.kill_n_collect.status=Támadás: {0} - Begyüjtés: {1} + +module.map_travel.status.has_next=Utazás ide: {0}, következő térkép: {1} +module.map_travel.status.no_next=Utazás ide {0} + +module.disconnect.status=Szétválasztás ({0}): {1} +module.disconnect.status_stopped=Szétválasztva ({0}) +module.disconnect.status_paused=Megállítva ({0}), újracsatlakozás {1} +module.disconnect.reason.break=Szünet megtartása +module.disconnect.reason.honor=Becsületpont vesztés + +# Players +gui.players.not_found.title=Felhasználó nem található +gui.players.not_found.by_name=Felhasználó ilyen névvel: {0}, nem található.\nPróbálkozz a felhasználó ID-jével, a hozzáadás során. +gui.players.not_found.by_id=Felhasználó ilyen ID-vel: {0} nem található.\nLegyél biztos benne, hogy ugyanazon a szerveren játszotok. + +# Issues +bot.issue.module_load_failed=Sikertelen kísérlet a Modul betöltésére {0}, alapértelmezett használata +bot.issue.config_load_failed=A konfigurációs fájl betöltése nem sikerült.\nA bot az alapértelmezett konfigurációt fogja használni és nem ment el semmit.\nTöröld ki a config.json fájlt (törölni fogja a konfigot) és indísd újra a BOTot. +bot.issue.feature.failed_to_tick=Nem sikerült a bejelölés +bot.issue.feature.failed_to_load=A funkció betöltése sikertelen +bot.issue.feature.failed_to_load.desc={0} nem lehet megfelelően regisztrálni: {1} + +# Verifier +verifier.discord_warn.title=Csatlakozz az eredeti Discordhoz! +verifier.discord_warn.content.1=Ez a Bot ingyenes, ha fizettél érte vagy reklámokat néztél meg érte, akkor át lettél verve. +verifier.discord_warn.content.2=Legyél biztos benne hogy, az eredeti Discordhoz csatlakoztál, hogy ingyenesen megkapd a fejlesztéseket. +verifier.discord_warn.checkbox.dont_show=Ne mutasd többször +verifier.discord_warn.button.join_discord=Csatlakozás a Discordhoz +verifier.discord_warn.button.ignore=Kihagyom + +verifier.authenticate.error=A botnak az indítása sikertelen. Próbáld meg újraindítani a gépedet. +verifier.authenticate.title=Discord azonosítás +verifier.authenticate.content=Kövesd az utasításokat a böngésződben, NE ZÁRD BE EZT AZ ABLAKOT!\nAz azonosítás lokális, semmilyen adatodad nem küldjük ki semmilyen szerverre.\nHa az ablak nem nyílik meg a böngésződben, használd a Chrome-ot vagy kapcsold ki a vírusírtót. + +verifier.panel.invalid_auth=Azonosítás sikertelen, indísd újra a Botot. +verifier.panel.authenticated.no_server=Azonosítva lettél ezen a néven: {0}
Nem csatlakoztál egyik szerverhez sem. +verifier.panel.authenticated.servers=Azonosítva lettél ezen a néven: {0}, szervereid:
    {1}
+verifier.panel.authenticated.join_official=Csatlakozz az eredeti Discord szerverhez hogy hozzáférj a fejlesztésekhez!
+verifier.panel.authenticated.blacklisted=Tagja vagy feketelistás szervereknek, akik árusítják a munkámat az engedélyem nélkül.
Muszáj kilépned a feketelistás szerverekről mielőtt használnád a Botot. Kattints az "Update"-re ha kiléptél.
" + +# Language changed +language.changed.title=A BOT nyelve megváltozott +language.changed.content=A BOT mostantól ezen a nyelven lesz elérhető: {0} készült {1} által.\nAjánlott újraindítani a Botot a változtatások mentéséért. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@dani#5703, @Sage#5941 & @ZsoleszKING#9919 \ No newline at end of file diff --git a/src/main/resources/lang/strings_it.properties b/src/main/resources/lang/strings_it.properties new file mode 100644 index 00000000..61cf7f6c --- /dev/null +++ b/src/main/resources/lang/strings_it.properties @@ -0,0 +1,339 @@ +# Config tabs +tabs.general=Generale +tabs.general.desc=Impostazioni generali per il bot +tabs.zones=Zone della mappa +tabs.zones.desc=Seleziona le zone preferite, da evitare, e sicure +tabs.preferred_zones=Aree preferite +tabs.preferred_zones.desc=Zone in cui il bot girerà randomicamente +tabs.avoided_zones=Aree evitate +tabs.avoided_zones.desc=Zone trattate come barriere dal bot +tabs.safety_places=Posti sicuri +tabs.safety_places.desc=Posti sicuri in cui scappare dai nemici o per riparare +tabs.players=Players +tabs.players.desc=Sezione Player per configurare altri giocatori o account +tabs.plugins=Plugins +tabs.plugins.desc=Informazioni e configurazioni per i plugin installati + +# Config tree +config.general=Generale +config.general.current_module=Modulo +config.general.working_map=Mappa +config.general.offensive=configurazione offensiva +config.general.offensive.desc=Utilizzato per uccidere gli alieni +config.general.roam=Config di viaggio +config.general.roam.desc=Configurazione per la navigazione e ricerca di alieni +config.general.run=Configurazione per scappare +config.general.run.desc=Utilizzato per correre in sicurezza o passare da una mappa all'altra +config.general.formation_check=Controlla formazione +config.general.formation_check.desc=Controlla formazione ogni X secondi, settare 0 per disabilitare +config.general.safety=Sicurezza +config.general.safety.repair_hp_range=Mantieni la salute tra +config.general.safety.repair_hp_range.desc=Se stai sparando ad un alieno, scappa via sotto questi hp +config.general.safety.repair_hp_no_npc=Ripara quando stai viaggiando & hp sotto +config.general.safety.repair_hp_no_npc.desc=Scappa quando è sotto quegli hp +config.general.safety.repair_to_shield=Riparare finchè lo scudo +config.general.safety.repair_to_shield.desc=Scudo minimo per smettere di riparare +config.general.safety.repair=Config di riparazione +config.general.safety.repair.desc=Usata per riparare dopo che la configurazione per correre ha gli scudi carichi +config.general.safety.max_deaths=Morti massime +config.general.safety.max_deaths.desc=Numero massimo di morti, se raggiunto, il bot va in pausa +config.general.safety.revive_location=Luogo di respawn +config.general.safety.revive_location.desc=Dove riparare l'astronave +config.general.safety.revive_location.list.base=Base +config.general.safety.revive_location.list.portal=Portale +config.general.safety.revive_location.list.spot=Sul posto +config.general.safety.wait_before_revive=Attendi prima di rivivere (sec) +config.general.safety.wait_before_revive.desc=Secondi da aspettare prima di far tornare in vita l'astronave +config.general.safety.wait_after_revive=Attendi dope essere respawnato (sec) +config.general.safety.wait_after_revive.des=Secondi da attendere dopo che la nave è stata aggiustata, consente alla nave di ripararsi +config.general.running=Scappando +config.general.running.run_from_enemies=Scappa dai nemici +config.general.running.run_from_enemies.desc=Se il bot dovrebbe scappare dai nemici che ti stanno attaccando, o che ti hanno attaccato recentemente +config.general.running.remember_enemies_for=Ricorda nemici per +config.general.running.remember_enemies_for.desc=per quanto tempo il bot si ricorda dei nemici ostili +config.general.running.run_from_enemies_sight=Scappa dai nemici nelle vicinanze +config.general.running.run_from_enemies_sight.desc=Scappa dai nemici non appena entrano nella tua minimappa +config.general.running.stop_running_no_sight=Smettila di scappare quando i nemici non sono più nelle vicinanze +config.general.running.stop_running_no_sight.desc=Smetterà di scappare se i nemici non ti stanno attaccando e non sono in vista +config.general.running.max_sight_distance=Distanza massima di vista +config.general.running.max_sight_distance.desc=non cosiderare nulla dopo questa distanza +config.general.running.ship_ability=Abilità nave +config.general.running.ship_ability.desc=Attivata quando stai scappando via +config.general.running.ship_ability_min=Distanza minima per abilità nave +config.general.running.ship_ability_min.desc=Distanza minima di sicurezza per usare l'abilità +config.general.running.run_furthest_port=Distanza portale più vicina max +config.general.running.run_furthest_port.desc=Corri verso il portale più lontano dal nemico, a meno che la distanza del portale non sia inferiore a max +config.general.roaming=Navigazione e comportamento dell'area preferita +config.general.roaming.keep=Continua il roaming verso lo stesso punto fino al raggiungimento +config.general.roaming.sequential=Spostati nelle zone preferite in ordine +config.general.roaming.only_kill_preferred=Uccidi gli alieni solamente se in area preferita +config.general.roaming.only_kill_preferred.desc=Seleziona e uccidi gli alieni solo quando si trovano all'interno di un'area preferita + +config.collect=Raccogliere +config.collect.stay_away_from_enemies=Stai lontando dai nemici +config.collect.auto_cloack=Auto camuffamento +config.collect.auto_cloack_key=Auto camuffamento tasto +config.collect.radius=Raggio di raccolta mentre killi +config.collect.radius.desc=Raggio di raccolta delle risorse durante l'uccisione di alieni + +config.collect.box_table.resource=Risorse +config.collect.box_table.resource.desc=Boxette o altre risorse che il bot ha visto +config.collect.box_table.collect=Raccogli +config.collect.box_table.collect.desc=Se la risorsa deve venir raccolta +config.collect.box_table.wait=Attendi (ms) +config.collect.box_table.wait.desc=Tempo da attendere per raccogliere la risorsa, in millisecondi +config.collect.box_table.priority=Priorità +config.collect.box_table.priority.desc=Priorità #1 verrà raccolta prima di priorità #2 + +config.loot=Uccidi alieni +config.loot.sab=Sab +config.loot.sab.desc=Sab automatiche per sopravvivere meglio +config.loot.sab.enabled=Abilitato +config.loot.sab.key=Tasto Sab +config.loot.sab.percent=Nave sotto +config.loot.sab.npc_amount=Scudo minimo alieno +config.loot.rsb=Rsb +config.loot.rsb.desc=Usa le munizioni RSB sugli alieni col campo UR +config.loot.rsb.enabled=Abilitato +config.loot.rsb.key=Tasto +config.loot.rsb.ammo_refresh=Attesa Rsb +config.loot.ammo_key=Tasto munizioni +config.loot.ship_ability=Tasto abilità offensiva +config.loot.ship_ability_min=Abilità offensiva min salute +config.loot.ship_ability_min.desc=Vita minima alieno per usare abilità +config.loot.max_circle_iterations=Movimento circolare +config.loot.max_circle_iterations.desc=Mosse massime per guardare avanti per cambiare la direzione del cerchio, 0 per disabilitare, raccomandato 3-5 +config.loot.run_config_in_circle=Configurazione da corsa per inseguire +config.loot.run_config_in_circle.desc=Usa la configurazione veloce per inseguire gli alieni che scappano +config.loot.group_npcs=Raggruppa gli alieni similari +config.loot.group_npcs.desc=Raggruppare gli alieni nello stesso GG nella tabella NPC +config.loot.npc_distance_ignore=Ignora alieni oltre + +config.loot.npc_table.name=Nome +config.loot.npc_table.name.desc=Nome npc, npc che finiscono con * sono raggruppati +config.loot.npc_table.radius=Raggio +config.loot.npc_table.radius.desc=Distanza da tenere dall'npc +config.loot.npc_table.priority=Priorità +config.loot.npc_table.priority.desc=priorità #1 verrà taggata prima di priorità #2 +config.loot.npc_table.kill=Killa +config.loot.npc_table.kill.desc=Se questo npc deve venir killato +config.loot.npc_table.attack_key=Tasto munizioni +config.loot.npc_table.attack_key.desc=Munizioni speciali per questo npc, se non selezionato viene usato quello di default +config.loot.npc_table.attack_formation=Formazione +config.loot.npc_table.attack_formation.desc=Formazione speciale per questo npc, se non usata, il bot userà la formazione di attacco +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Opzioni extra per questo npc + +config.loot.npc_table.extra.no_circle=Non girare +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=Non girare attorno all'npc, resta all'interno del raggio +config.loot.npc_table.extra.ignore_ownership=Ignora proprietario +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Continua a killare l'npc anche se è stato già attaccato da altri +config.loot.npc_table.extra.ignore_attacked=Ignora alieno già attaccato +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Seleziona l'npc anche se altri giocatori lo stanno già attaccando +config.loot.npc_table.extra.passive=Passivo +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Attacca l'npc solamente se ti sta già attaccando +config.loot.npc_table.extra.attack_second=Attacca se attaccato +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Spara solo se altri stanno già attaccando
Deve essere selezionato anche ignora proprietario e ignora attaccato +config.loot.npc_table.extra.use_rsb=Usa Rsb +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Usa le Rsb per attaccare questo npc +config.loot.npc_table.extra.pet_locator=Individuatore Pet +config.loot.npc_table.extra.pet_locator.short=PL +config.loot.npc_table.extra.pet_locator.desc=Usa il localizzatore del pet per trovare gli npc + +config.pet=Pet +config.pet.enabled=Usa pet +config.pet.enabled.desc=Se disabilitato, il bot non utilizzerà la pet +config.pet.module_id=Moduli Pet +config.pet.module.desc=La lista si auto aggiornerà se il tuo pet è attivo + +config.group=Gruppo +config.group.accept_invites=Accetta inviti +config.group.accept_invites.desc=Il bot accetterà gli inviti +config.group.whitelist_tag=Accetta gli inviti da +config.group.whitelist_tag.desc=Accetta solo gli inviti dagli utenti in whitelist +config.group.invite_tag=Invia invito a +config.group.invite_tag.desc=Invia invito al player con questa tag +config.group.open_invites=Apri inviti (Leader) +config.group.open_invites.desc=Consenti tutti di invitare altri +config.group.kick_tag=Espelli player (Leader) +config.group.kick_tag.desc=Espelli player dal gruppo + +config.miscellaneous=Altro +config.miscellaneous.refresh_time=Refresh ogni +config.miscellaneous.refresh_time.desc=Ogni quanti minuti riavviare la pagina per evitare lag +config.miscellaneous.pause_for=Pausa dopo il refresh +config.miscellaneous.pause_for.desc=Per quanti minuti mettere in pausa dopo un refresh +config.miscellaneous.drone_repair_percentage=Percentuale per riparare UAV +config.miscellaneous.drone_repair_percentage.desc=I droni vengono riparati se superiori a questa percentuale + +config.bot_settings=Impostazioni bot +config.bot_settings.lang=Lingua +config.bot_settings.zone_resolution=Precisione zona +config.bot_settings.zone_resolution.desc=Quantità di suddivisioni della mappa durante la selezione delle zone +config.bot_settings.map_start_stop=Avvia/Pausa cliccando sulla mappa +config.bot_settings.map_start_stop.desc=Tasto sinistro per avvia/pausa. Tasto destro per muovere la nave. +config.bot_settings.confirm_exit=Confermare uscita +config.bot_settings.confirm_exit.desc=Confermare prima di uscire dal bot +config.bot_settings.min_tick=Tempo minimo di tick +config.bot_settings.dev_stuff= Mostra roba per sviluppatori +config.bot_settings.dev_stuff.desc=Abilitando questo, il tuo bot utilizzerà più CPU. +config.bot_settings.display=Display +config.bot_settings.display.show_names=Mostra i nomi utente +config.bot_settings.display.show_names.desc=Mostra i nomi utente sulla minimappa +config.bot_settings.display.hide_name=Nascondi nome +config.bot_settings.display.hide_name.desc=Nascondi il nome dell'eroe sulla mappa +config.bot_settings.display.hide_group_names=Nascondi nomi del gruppo +config.bot_settings.display.hide_group_names.desc=Nasconde nomi del gruppo nella mappa +config.bot_settings.display.trail_length=Lunghezza scia +config.bot_settings.display.trail_length.desc=Tempo che la scia deve restare sulla mappa, in secondi +config.bot_settings.display.show_zones=Mostra le zone nella mappa principale +config.bot_settings.display.show_zones.desc=Abilita per mostrare le zone evitate / preferite sulla mappa +config.bot_settings.display.button_size=Dimensione pulsante GUI +config.bot_settings.display.button_size.desc=Cambia scheda nella finestra principale di configurazione e ridimensionamento per l'aggiornamento + +# Safety places +safety_places.type.portal=Portale +safety_places.type.cbs=CBS +safety_places.type.base=Base + +safety_places.run_mode= [Tutto] Usa se +safety_places.run_mode.always=Sempre +safety_places.run_mode.enemy_flee_only=Volando (Nemico in vista) +safety_places.run_mode.repair_only=Riparando (No nemici) +safety_places.run_mode.refresh=Aggiorna +safety_places.run_mode.never=Mai + +safety_places.jump_mode= [Portale] Portala se +safety_places.jump_mode.never=mai +safety_places.jump_mode.escaping=Scappando (Nemico ti sta sparando) +safety_places.jump_mode.fleeing=Volando (Nemico in vista) +safety_places.jump_mode.repairing=Riparando +safety_places.jump_mode.always=Sempre +safety_places.jump_mode.always_other_side=Sempre (Ripara nell'altro lato) + +safety_places.cbs_mode= [CBS] Usa se +safety_places.cbs_mode.ally=CBS alleata +safety_places.cbs_mode.ally_neutral=Alleata o vuota + +#Players +players.add_player=Aggiungi player +players.add_player.by_name=Utente dal nome +players.add_player.by_id=Utente dall'ID +players.add_player.username=Username: +players.add_player.user_id=User ID: +players.add_player.invalid_id=User ID Invalido +players.add_player.invalid_id.no_number={0} Non è un numero valido +players.remove_player.title=Sei sicuro? +players.remove_player.msg=Vuoi veramente eliminare il player {0} selezionato? + +players.tag_default.unset=non impostata +players.tag_default.all=Tutti +players.tag_default.none=Nessuno +players.select_players.warn.title=Seleziona i player prima +players.select_players.warn.add=Seleziona i giocatori che vuoi aggiungere +players.select_players.warn.remove=Seleziona i giocatori che vuoi rimuovere +players.select_players.warn.remove_player=Seleziona il player che vuoi rimuovere +players.delete_tag.confirm.title=Sei sicuro? +players.delete_tag.confirm.msg=Vuoi eliminare {0} tag, e rimuoverlo da tutti? + +# Plugins +plugins.reload=Ricarica i plugin +plugins.config_button.enabled.desc=Mostra configurazione +plugins.config_button.disabled.desc=Configurazione disabilitata, feature non caricata +plugins.config_button.popup=Impossibile modificare la configurazione +plugins.config_button.popup.desc=Configurazione non disponibile per feature non caricate \nAbilita la feature e caricala, dopo di che riprova. + +plugins.issues.invalid_json=plugin.json non valido +plugins.issues.invalid_json.desc=La versione minima {0} è superiore alla versione supportata {1} +plugins.issues.bot_update=Aggiornamento del bot richiesto +plugins.issues.bot_update.desc=Questo plugin richiede {0}, quindi non può funzionare in DarkBot v{1} +plugins.issues.loaded_twice=Plugin già caricato +plugins.issues.loaded_twice.desc=Un plugin con lo stesso nome è già stato caricato +plugins.issues.plugin_update=Potrebbe essere necessario aggiornare il plug-in +plugins.issues.plugin_update.desc=Il plugin è fatto per {0}, quindi potrebbe non funzionare in DarkBot v{1} + +plugins.issues.signature.plugin_not_signed=Plugin non firmato +plugins.issues.signature.plugin_not_signed.desc=Questo plugin non è stato firmato o ha una firma non valida +plugins.issues.signature.unknown_signature=Firma sconosicuta +plugins.issues.signature.unknown_signature.desc=Questo plugin è stato firmato con un certificato non attendibile +plugins.issues.signature.invalid_signature=Firma invalida +plugins.issues.signature.invalid_signature.desc=Il plugin ha una firma non valida o è stato manomesso + +# Main GUI +gui.hamburger_button.home=HOME +gui.hamburger_button.reload=RICARICA +gui.hamburger_button.copy_sid=COPIA SID +gui.hamburger_button.discord=DISCORD +gui.config_button=Mostra/nascondi configurazione +gui.start_button=Start/Pausa bot +gui.visibility_button=Mostra/Nascondi browser +gui.pin_button=Sempre in cima + +gui.exit=Sei sicuro di voler uscire? +gui.exit.desc=Conferma l'uscita dal bot, può essere disabilitato in Impostazioni> Impostazioni bot +gui.exit.yes_button=Si +gui.exit.cancel_button=Annulla + +gui.map.info=v{0} - Refresh: {1}/{2} +gui.map.loading=Caricamento +gui.map.waiting=In attesa {0} +gui.map.running=In esecuzione {0} + +# Modules +module.instructions.title=Istruzioni +module.kill_n_collect.status=Kill: {0} - Raccolta: {1} + +module.map_travel.status.has_next=Viaggiando verso {0}, prossima mappa: {1} +module.map_travel.status.no_next=Viaggiando verso {0} + +module.disconnect.status=Disconnessione ({0}): {1} +module.disconnect.status_stopped=Disconnesso ({0}) +module.disconnect.status_paused=In pausa ({0}), riconnessione in {1} +module.disconnect.reason.break=facendo una pausa +module.disconnect.reason.honor=onore perso +module.disconnect.reason.draw_fire=Il nemico ha usato draw fire + +# Players +gui.players.not_found.title=Utente non trovato +gui.players.not_found.by_name=Utente con questo nome {0} non può essere trovato.\nProva ad aggiungere l'utente tramite l'ID utente +gui.players.not_found.by_id=Utente con questo ID {0} non può essere trovato.\nAssicurati che appartenga al tuo server + +# Issues +bot.issue.module_load_failed=Caricamento del modulo non riuscito {0}, uso default +bot.issue.config_load_failed=Impossibile caricare il file di configurazione.\nIl bot utilizzerà la configurazione predefinita e non verrà salvato.\nElimina il file config.json (cancellerà le configurazioni) e riavvia il bot. +bot.issue.feature.failed_to_tick=Tick fallito +bot.issue.feature.failed_to_load=Impossibile caricare la feature +bot.issue.feature.failed_to_load.desc={0} Non può essere registrato correttamente: {1} + +bot.issue.resolution=cambiare risoluzione a
è un tuo rischio!
Se hai problemi,
resetta a default! + +# Verifier +verifier.discord_warn.title=Entra nel discord ufficiale! +verifier.discord_warn.content.1=Questo bot è gratuito, se paghi per averlo o guardi ads sei stato imbrogliato! +verifier.discord_warn.content.2=Verifica di essere nel server ufficiale per avere l'ultima versione gratuitamente. +verifier.discord_warn.checkbox.dont_show=Non mostrare più +verifier.discord_warn.button.join_discord=Entra su discord +verifier.discord_warn.button.ignore=Ignora + +verifier.authenticate.error=Avvio fallito, prova a riavviare il pc +verifier.authenticate.title=Discord auth +verifier.authenticate.content=Segui le istruzione nel tuo browser, non chiudere questa finestra.\nL'autenticazione è locale e non viene inviato nulla a nessun server.\nSe la pagina non viene caricata sul tuo browser, prova ad usare chrome e/o disabilitare l'antivirus. + +verifier.panel.invalid_auth=Autenticazione invalida, prova a restartare il bot +verifier.panel.authenticated.no_server=Sei stato autenticato come {0}
Sembra che tu non sia su nessun server. +verifier.panel.authenticated.servers=Sei stato autenticato come {0}, membro di:
    {1}
+verifier.panel.authenticated.join_official=Puoi entrare nel server ufficiale per scaricare aggiornamenti!
+verifier.panel.authenticated.blacklisted=Sei membro di un server in blacklist che ridistribuisce il mio lavoro senza consenso.
Devi uscire dal server in blacklist prima di poter usare il bot. Fai clic per aggiornare dopo che te ne sei andato.
+ +# Language changed +language.changed.title=Linguaggio del bot cambiato +language.changed.content=È ora in uso la traduzione in {0} creata da {1}.\nSi consiglia di riavviare il bot per apportare le modifiche. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@Fabio#6652 \ No newline at end of file diff --git a/src/main/resources/lang/strings_pl.properties b/src/main/resources/lang/strings_pl.properties new file mode 100644 index 00000000..4e3c354a --- /dev/null +++ b/src/main/resources/lang/strings_pl.properties @@ -0,0 +1,334 @@ +# Config tabs +tabs.general=Ogólne +tabs.general.desc=Ogólne ustawienia dotyczące bota +tabs.preferred_zones=Preferowane Miejsca +tabs.preferred_zones.desc=Miejsca do których bot będzie losowo podróżować +tabs.avoided_zones=Omijanie Miejsc +tabs.avoided_zones.desc=Miejsca traktowane przez bota jako bariery nie do przekroczenia +tabs.safety_places=Bezpieczne Miejsca +tabs.safety_places.desc=Bezpieczne miejsca do podrózowania lub uciekania przed wrogami +tabs.players=Gracze +tabs.plugins.desc=Informacje oraz konfiguracje dla zainstalowanych pluginów + +# Config tree +config.general=Ogólne +config.general.current_module=Moduł +config.general.working_map=Mapa robocza +config.general.offensive=Ofensywna konfiguracja +config.general.offensive.desc=Używana do zabijania NPCs +config.general.roam=Podróżnicza konfiguracja +config.general.roam.desc=Używana do podróżowania po mapie oraz szukania NPCs +config.general.run=Ucieczkowa konfiguracja +config.general.run.desc=Używana do uciekania w bezpieczne miejsce lub do zmiany map +config.general.formation_check=Sprawdź Formacje +config.general.formation_check.desc=Sprawdza formacje co x sekund, ustaw 0 aby dezaktywować +config.general.safety=Bezpieczeństwo +config.general.safety.repair_hp_range= Utrzymuj zdrowie pomiędzy +config.general.safety.repair_hp_range.desc=Gdy HP osiągnie wartość minimalną przerywa atak i ucieka do portu +config.general.safety.repair_hp_no_npc=Napraw gdy latasz & HP jest poniżej +config.general.safety.repair_hp_no_npc.desc= HP do uciekania gdy lata po mapie +config.general.safety.repair_to_shield=Naprawiaj osłony aż +config.general.safety.repair_to_shield.desc=Minimum osłon aby przestać naprawiać +config.general.safety.repair=Naprawiająca konfiguracja +config.general.safety.repair.desc=Służy do naprawy osłon po ucieczce +config.general.safety.max_deaths=Maks śmierci +config.general.safety.max_deaths.desc=Maksymalna ilość śmierci, jeżeli zostanie osiągnięta, bot się wyłączy +config.general.safety.revive_location=Miejsce odrodzenia +config.general.safety.revive_location.desc=Gdzie bot ma się odradzać +config.general.safety.revive_location.list.base=Baza +config.general.safety.revive_location.list.portal=Portal +config.general.safety.revive_location.list.spot=Na miejscu +config.general.safety.wait_before_revive=Poczekaj przed odrodzeniem (sec) +config.general.safety.wait_before_revive.desc=Czas jaki bot ma odczekać przed odrodzeniem +config.general.safety.wait_after_revive=Poczekaj po odrodzeniu +config.general.safety.wait_after_revive.des=Czas jaki bot ma odczekać po odrodzeniu (np. w celu naprawy statku) +config.general.running=Uciekanie +config.general.running.run_from_enemies=Ucieka przed wrogami +config.general.running.run_from_enemies.desc=Ucieka przed wrogami, włącznie z tymi którzy robili to wcześniej +config.general.running.remember_enemies_for=Pamiętaj wrogów przez +config.general.running.remember_enemies_for.desc=Przez jaki czas ma pamiętać wrogów którzy strzelali do nas +config.general.running.run_from_enemies_sight=Ucieka przed wrogami w polu widzenia +config.general.running.run_from_enemies_sight.desc=Uciekaj przed wrogami, gdy tylko wejdą na Twoją minimapę +config.general.running.stop_running_no_sight=Przestań uciekać gdy nie widać wroga +config.general.running.stop_running_no_sight.desc=Przestanie uciekać gdy wrogowie nie atakują i nie są w polu widzenia +config.general.running.max_sight_distance=Maksymalne pole widzenia +config.general.running.max_sight_distance.desc=Nie zwraca uwagi na wrogów w zasięgu pola widzenia jeżeli są dalej niż te pole +config.general.running.ship_ability=Umiejętność statku +config.general.running.ship_ability.desc=Używana podczas ucieczki przed wrogami którzy nas atakują +config.general.running.ship_ability_min=Minimalny dystans użycia umiejętności statku +config.general.running.ship_ability_min.desc=Minimalny bezpieczny dystans użycia umiejętności statku +config.general.running.run_furthest_port=Maksymalna odległość do nabliższego portu +config.general.running.run_furthest_port.desc=Ucieka do portu daleko przed wrogami dopóki dystans portu jest poniżej maksymalnej +config.general.roaming=Podróżowanie & Preferowane zachowanie +config.general.roaming.keep=Podrózuje dopóki ten sam punkt nie zostanie uzyskany +config.general.roaming.sequential=Podrózuje do preferowanych miejsc dopóki nie zostaną uzyskane +config.general.roaming.only_kill_preferred=Zabija NPCs w preferowanych miejscach +config.general.roaming.only_kill_preferred.desc=Wybiera i zabija NPCs tylko jeżeli są w preferowanych miejscach + +config.collect=Zbieranie +config.collect.stay_away_from_enemies=Uciekaj przed wrogami +config.collect.auto_cloack=Auto niewidka +config.collect.auto_cloack_key=Auto niewidka przycisk +config.collect.radius=Dystans zbierania podczas zabijania +config.collect.radius.desc=Dystans zbierania zasobów gdy zabijasz NPCs + +config.collect.box_table.resource=Zasoby +config.collect.box_table.resource.desc=Skrzynki lub inne zasoby które bot napotkał +config.collect.box_table.collect=Zbierz +config.collect.box_table.collect.desc=Naciśnij aby zasób ma być zbierany +config.collect.box_table.wait=Czas (ms) +config.collect.box_table.wait.desc=Czas do poczekania aby podnieść zasób, w milisekundach +config.collect.box_table.priority=Priorytet +config.collect.box_table.priority.desc=Priorytet #1 zostanie zebrany przed priorytetem #2 + +config.loot=Zabijanie NPC +config.loot.sab=Sab +config.loot.sab.desc=Automatycznie używa Sab-a na NPCs aby przetrwać dłużej +config.loot.sab.enabled=Włączone +config.loot.sab.key=Przycisk +config.loot.sab.percent=Osłona statku poniżej +config.loot.sab.npc_amount=Minimalna osłona u NPC +config.loot.rsb=RSB +config.loot.rsb.desc=Używa amunicji RSB na alienach z flagą UR +config.loot.rsb.enabled=Włączone +config.loot.rsb.key=Przycisk +config.loot.rsb.ammo_refresh=Odnowienie RSB +config.loot.ammo_key=Przycisk amunicji +config.loot.ship_ability=Przycisk ofensywnej umiejętności +config.loot.ship_ability_min=Ofensywna umiejętność minimalne HP +config.loot.ship_ability_min.desc=Minimalne HP NPC do użycia umiejętności +config.loot.max_circle_iterations=Inteligentny obrót +config.loot.max_circle_iterations.desc=Maksymalna ilość ruchów do kręcenia kółek 0 aby wyłączyć, polecane 3-5 +config.loot.run_config_in_circle=Używaj konfiguracji do latania za celem +config.loot.run_config_in_circle.desc=Używaj konfiguracji do ucieczki aby śledzić uciekających NPCs +config.loot.group_npcs=Grupuje podobne NPCs +config.loot.group_npcs.desc=Grupuje NPCs w tych samych GG w tabeli +config.loot.npc_distance_ignore=Ignoruje wrogów dalej niż + +config.loot.npc_table.name=Nazwa +config.loot.npc_table.name.desc=Nazwy NPC, NPC kończące się na * są pogrupowane +config.loot.npc_table.radius=Dystans +config.loot.npc_table.radius.desc=Odległość do zachowania od tego NPC +config.loot.npc_table.priority=Priorytet +config.loot.npc_table.priority.desc=Priorytet #1 będzie celem przed priorytetem #2 +config.loot.npc_table.kill=Zabij +config.loot.npc_table.kill.desc=Jeżeli ten NPC ma zostać zabity +config.loot.npc_table.attack_key=Klawisz amunicji +config.loot.npc_table.attack_key.desc=Specjalna amunicja dla tego NPC, jeżeli jest nieużywana, zostanie użyta domyślna +config.loot.npc_table.attack_formation=Formacja +config.loot.npc_table.attack_formation.desc=Formacja specjalna dla danego NPC, jeżeli nie używana, zostanie użyta formacja z konfiguracji ofensywnej +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Kilka dodatkowych flag dla NPC + +config.loot.npc_table.extra.no_circle=Bez kręcenia +config.loot.npc_table.extra.no_circle.short=BK +config.loot.npc_table.extra.no_circle.desc=Nie kręć NPC, po prostu zostań w środku dystansu +config.loot.npc_table.extra.ignore_ownership=Ignoruj właściciela +config.loot.npc_table.extra.ignore_ownership.short=IW +config.loot.npc_table.extra.ignore_ownership.desc=Kontynuuj zabijanie NPC, nawet jeśli ma on biały okrąg +config.loot.npc_table.extra.ignore_attacked=Ignoruj zaatakowanego +config.loot.npc_table.extra.ignore_attacked.short=IZ +config.loot.npc_table.extra.ignore_attacked.desc=Wybierz NPC nawet jeżeli inni gracze już do niego strzelają +config.loot.npc_table.extra.passive=Pasywny +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Bądź pasywny w stosunku do tego NPC, strzelaj tylko jeśli NPC do Ciebie strzela +config.loot.npc_table.extra.attack_second=Atakuj drugi +config.loot.npc_table.extra.attack_second.short=AD +config.loot.npc_table.extra.attack_second.desc=Strzelaj tylko wtedy, gdy inni już atakują.
Musisz także wybrać opcję Ignoruj zaatakowanego i Ignoruj właściciela +config.loot.npc_table.extra.use_rsb=Użyj RSB +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Użyj RSB do zaatakowania tego NPC +config.loot.npc_table.extra.pet_locator=Pet lokalizator +config.loot.npc_table.extra.pet_locator.short=PL +config.loot.npc_table.extra.pet_locator.desc=Użyj PET Lokalizatora aby znaleść NPC + +config.pet=Pet +config.pet.enabled=Używaj Peta +config.pet.enabled.desc=Jeżeli wyłączone, bot zignoruje wszystkie funkcje PETA +config.pet.module_id=Moduł PET'a +config.pet.module=# Moduł do użycia +config.pet.module.desc=0 - pasywny
1 - tryb ochronny
2 - cokolwiek dalej + +config.group=Grupa +config.group.accept_invites=Zaakceptuj zaproszenia +config.group.accept_invites.desc=Sprawia, że bot akceptuje zaproszenia +config.group.whitelist_tag=Akceptuj zaproszenia od +config.group.whitelist_tag.desc=Akceptuje zaproszenia tylko od graczy z whitelisty +config.group.invite_tag=Wyślij zaproszenia do +config.group.invite_tag.desc=Wysyła zaproszenia do graczy z tagiem +config.group.open_invites=Otwórz zaproszenia (Lider) +config.group.open_invites.desc=Pozwól wszystkim zapraszać innych +config.group.kick_tag=Wyrzuć graczy (Lider) +config.group.kick_tag.desc=Wyrzuć graczy z grupy + +config.miscellaneous=Różne +config.miscellaneous.refresh_time=Odświeżaj co +config.miscellaneous.refresh_time.desc=Co ile minut ma się odświeżać +config.miscellaneous.pause_for=Pauza po odświeżeniu +config.miscellaneous.pause_for.desc=Ile minut pauzy po odświeżeniu +config.miscellaneous.drone_repair_percentage=Naprawa dronów procenty +config.miscellaneous.drone_repair_percentage.desc=Drony sa naprawiane powyżej (%) + +config.bot_settings=Ustawienia bota +config.bot_settings.locale=Język +config.bot_settings.lang=Język +config.bot_settings.zone_resolution=Precyzja strefy +config.bot_settings.zone_resolution.desc=Liczba map podzielona gdy wybierzesz strefy +config.bot_settings.map_start_stop=Start/pauza klikanie mapy +config.bot_settings.map_start_stop.desc=Lewy przycisk myszy do Start/pauza. Prawy myszy do ruszania statkiem +config.bot_settings.confirm_exit=Potwierdź wyjście +config.bot_settings.confirm_exit.desc=Potwierdź przed wyłączeniem bota +config.bot_settings.min_tick=Minimalny czas ticku +config.bot_settings.dev_stuff=Statystyki dla nerdów +config.bot_settings.dev_stuff.desc=Włączenie tego sprawi większe zużycie CPU przez bota. +config.bot_settings.display=Wygląd +config.bot_settings.display.show_names=Pokaż nazwy użytkowników +config.bot_settings.display.show_names.desc=Pokazuje nazwy użytkowników na minimapie +config.bot_settings.display.hide_name=Ukryj nazwę użytkownika +config.bot_settings.display.hide_name.desc=Ukrywa nazwy na minimapie +config.bot_settings.display.hide_group_names=Ukryj nazwy grup +config.bot_settings.display.hide_group_names.desc=Ukryj nazwy członków grupy na mapie +config.bot_settings.display.trail_length=Długość trajektorii +config.bot_settings.display.trail_length.desc=Czas trasy jaka powinna być na mapie, w sekundach +config.bot_settings.display.show_zones=Pokazuje strefy na głownej mapie +config.bot_settings.display.show_zones.desc=Włącz aby pokazać unikane/preferowane strefy na mapie +config.bot_settings.display.button_size=Wielkość przycisków oraz GUI +config.bot_settings.display.button_size.desc=Zmień wielkość kart oraz rozmiar okienek +config.bot_settings.display.hide_editors=Ukryj ustawienia edytora dopóki nie klikniesz +config.bot_settings.display.hide_editors.desc=Użyj konfiguracja tekstowej, który jest mniej intensywny na zasoby + +# Safety places +safety_places.type.portal=Portal +safety_places.type.cbs=Stacja Bazowa +safety_places.type.base=Baza + +safety_places.run_mode=[Wszystkie] Używaj warunków +safety_places.run_mode.always=Zawsze +safety_places.run_mode.enemy_flee_only=Odlatywanie (Wróg w zasięgu) +safety_places.run_mode.repair_only=Naprawa (brak wrogów) +safety_places.run_mode.refresh=Przeładuj +safety_places.run_mode.never=Nigdy + +safety_places.jump_mode=[Portal] Używaj +safety_places.jump_mode.never=Nigdy +safety_places.jump_mode.escaping=Ucieczka (Atak wroga) +safety_places.jump_mode.fleeing=Odlatywanie (Wróg w zasięgu) +safety_places.jump_mode.repairing=Naprawianie +safety_places.jump_mode.always=Zawsze +safety_places.jump_mode.always_other_side=Zawsze (naprawiaj po drugiej stronie) + +safety_places.cbs_mode=[Stacje bazowe] Używaj +safety_places.cbs_mode.ally=Sojusznicze bazy +safety_places.cbs_mode.ally_neutral=Sojusznicy lub puste +plugins.reload=Przeładuj pluginy + + +#Players +players.add_player=Dodaj gracza +players.add_player.by_name=Według nazwy gracza +players.add_player.by_id=Na podstawie ID gracza +players.add_player.username=Nazwa gracza +players.add_player.user_id=ID gracza: +players.add_player.invalid_id=Nieprawidłowe ID gracza +players.add_player.invalid_id.no_number={0} jest nieprawidłowym numerem. + +players.tag_default.unset=Nie ustawiono +players.tag_default.all=Wszyscy +players.tag_default.none=Nikt +players.select_players.warn.title=Ustaw najpierw graczy +players.select_players.warn.add=Wybierz graczy, do których chcesz dodać tag +players.select_players.warn.remove=Wybierz graczy, z których chcesz usunąć tag +players.delete_tag.confirm.title=Jesteś pewny? +players.delete_tag.confirm.msg=Czy chcesz usunąć {0} tag i usunąć go od wszystkich? + + +# Plugins +plugins.config_button.enabled.desc=Pokaż konfiguracje +plugins.config_button.disabled.desc=Konfiguracja jest zablokowana, funkcja nie została załadowana +plugins.config_button.popup=Nie można zedytować konfiguracji +plugins.config_button.popup.desc=Konfiguracja nie dostępna dla niezaładowanej funkcji \nWłącz funkcję i wczytaj ją, a następnie spróbuj ponownie + +plugins.issues.invalid_json=Nieprawidłowy plugin.json +plugins.issues.invalid_json.desc=Minimalna wersja {0} jest wyższa niż obsługiwana {1} +plugins.issues.bot_update=Wymagana aktualizacja Bota +plugins.issues.bot_update.desc=Ten plugin wymaga {0}, więc nie zadziała na wersji DarkBot v{1} +plugins.issues.plugin_update=Plugin może wymagać aktualizacji +plugins.issues.plugin_update.desc=Ten plugin został zrobiony dla {0}, więc może on nie działać na wersji DarkBot v{1} + +plugins.issues.signature.plugin_not_signed=Plugin nie podpisany +plugins.issues.signature.plugin_not_signed.desc=Ten plugin nie został podpisany bądź ma nieprawidłowy podpis +plugins.issues.signature.unknown_signature=Nieznany podpis +plugins.issues.signature.unknown_signature.desc=Plugin został podpisany niezaufanym certyfikatem +plugins.issues.signature.invalid_signature=Niewłaściwy podpis +plugins.issues.signature.invalid_signature.desc=Plugin ma nieprawidłowy podpis lub został zmieniony + +# Main GUI +gui.hamburger_button.home=START +gui.hamburger_button.reload=PRZEŁADUJ +gui.hamburger_button.copy_sid=SKOPIUJ SID +gui.hamburger_button.discord=DISCORD +gui.config_button=Pokaż/Ukryj konfigurację +gui.start_button=Start/Pauza bota +gui.visibility_button=Pokaż/Ukryj przeglądarkę +gui.pin_button=Zawsze na górze + +gui.exit=Czy jesteś pewny że chcesz wyjść? +gui.exit.desc=Potwierdzenie wyjścia z bota, można wyłączyć w ustawieniach> ustawienia bota +gui.exit.yes_button=Tak +gui.exit.cancel_button=Anuluj + +gui.map.loading=Wczytywanie +gui.map.info=v{0} - Przeładowanie: {1}/{2} +gui.map.waiting=OCZEKIWANIE {0} +gui.map.running=CZAS DZIAŁANIA {0} + +# Modules +module.instructions.title=Instrukcje +module.kill_n_collect.status=Zabijanie: {0} - Zbieranie: {1} + +module.map_travel.status.has_next=Podróżowanie do {0}, następna mapa: {1} +module.map_travel.status.no_next=Podróżowanie do {0} + +module.disconnect.status=Rozłączanie ({0}): {1} +module.disconnect.status_stopped=Rozłączony ({0}) +module.disconnect.status_paused=Pauza ({0}), ponowne połączenie za {1} +module.disconnect.reason.break=Branie przerwy +module.disconnect.reason.honor=Honor został stracony +module.disconnect.reason.draw_fire=Wróg użył ognia + +# Players +gui.players.not_found.title=Użytkownik nie znaleziony +gui.players.not_found.by_name=Użytkownik z nazwą {0} nie został znaleziony.\nSpróbuj dodać użytkownika za pomocą ID. +gui.players.not_found.by_id=Użytkownik z ID {0} nie został znaleziony.\nUpewnij się, że należy on do gracza na Twoim serwerze. + +# Issues +bot.issue.module_load_failed=Nie udało się załadować modułu {0}, użyje domyślnego +bot.issue.config_load_failed=Nie udało się załadować pliku konfiguracyjnego.\nBot użyje domyślnej konfiguracji i nie zapisze aktualnej.\nUsuń plik config.json (usunie Twoje ustawienia) i zrestartuj bota. +bot.issue.feature.failed_to_tick=Błąd modułu +bot.issue.feature.failed_to_load=Nie można załadować funkcji +bot.issue.feature.failed_to_load.desc={0} nie mogło zostać poprawnie zarejestrowane: {1} + +# Verifier +verifier.discord_warn.title=Dołącz na oficjalny serwer discord! +verifier.discord_warn.content.1=Ten bot jest darmowy, jeżeli zapłaciłeś za niego bądź oglądałeś reklamy, zostałeś oszukany! +verifier.discord_warn.content.2=Upewnij się, że jesteś na oficjalnym serwerze discord, aby otrzymywać najnowsze aktualizacje za darmo. +verifier.discord_warn.checkbox.dont_show=Nie pokazuj więcej tego komunikatu +verifier.discord_warn.button.join_discord=Dołącz na discorda +verifier.discord_warn.button.ignore=Ignoruj + +verifier.authenticate.error=Bot nie uruchomił się. Spróbuj ponownie uruchomić komputer. +verifier.authenticate.title=Autoryzacja Discord +verifier.authenticate.content=Postępuj zgodnie z instrukcjami w przeglądarce, nie zamykaj tego okna. \nUwierzytelnianie jest lokalne i nie wysyła żadnych danych do żadnego serwera. \nJęzeli strona się nie ładuje w przeglądarce spróbuj użyć Google Chrome lub wyłącz antyvirusa. + +verifier.panel.invalid_auth=Nieprawidłowe uwierzytelnienie, spróbuj ponownie uruchomić bota +verifier.panel.authenticated.no_server=Zostałeś zautoryzowany jako {0}
Wygląda na to, że nie jesteś na żadnym serwerze. +verifier.panel.authenticated.servers=Zostałeś zautoryzowany jako {0}, członek:
    {1}
+verifier.panel.authenticated.join_official=Powinieneś dołączyć na oficjalny serwer aby otrzymać aktualizacje!
+verifier.panel.authenticated.blacklisted=Jesteś członkiem serwerów z czarnej listy, które redystrybuują moją pracę bez mojej zgody.
Musisz opuścić serwery z czarnej listy przed użyciem bota. Kliknij aby zaktualizować jeżeli wyszedłeś.
+ +# Language changed +language.changed.title=Zmieniono język Bota +language.changed.content=Bot używa teraz tłumaczenia {0} utworzonego przez {1}.\nZalecane jest ponowne uruchomienie bota, aby w pełni zastosować zmiany. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@TeYroX#0101 \ No newline at end of file diff --git a/src/main/resources/lang/strings_pt.properties b/src/main/resources/lang/strings_pt.properties new file mode 100644 index 00000000..65841d39 --- /dev/null +++ b/src/main/resources/lang/strings_pt.properties @@ -0,0 +1,302 @@ +# Guias de configuração +tabs.general=Geral +tabs.general.desc=Configurações Gerais do bot +tabs.preferred_zones=Zonas preferidas +tabs.preferred_zones.desc=Zonas pelas quais o bot percorrerá aleatoriamente +tabs.avoided_zones=Zonas evitadas +tabs.avoided_zones.desc=Zonas tratadas como barreiras pelo bot +tabs.safety_places=Locais de segurança +tabs.safety_places.desc=Lugares seguros para onde fugir para reparar ou escapar de inimigos +tabs.plugins.desc=Informações e configurações dos plugins instalados + +# Config tree +# - Geral. +config.general=Geral +config.general.current_module=Módulo +config.general.working_map=Mapa de trabalho +config.general.offensive=Configuração ofensiva +config.general.offensive.desc=Usado para matar NPCs +config.general.roam=Configuração de percurso +config.general.roam.desc=Usado para percorrer o mapa, procurando por NPCs +config.general.run=Executar configuração +config.general.run.desc=Usado para fugir em segurança ou alternar entre mapas +config.general.formation_check=Verificar formação +config.general.formation_check.desc=Verifica a formação a cada X segundos, defina 0 para desativar +# - Geral + Segurança. +config.general.safety=Segurança +config.general.safety.repair_hp_range=Mantenha a saúde entre +config.general.safety.repair_hp_range.desc=Mesmo se atacndo um npc, fuja quando estiver sob este HP +config.general.safety.repair_hp_no_npc=Reparar quando estiver em percurso e hp em +config.general.safety.repair_hp_no_npc.desc=Hp para rodar quando apenas estiver percorrendo o mapa +config.general.safety.repair_to_shield=Repare escudo ate +config.general.safety.repair_to_shield.desc=Escudo mínimo para parar de reparar +config.general.safety.repair=Configuração reparo +config.general.safety.repair.desc=Usado para reparar após os escudos de formação de corrida estarem cheios +config.general.safety.max_deaths=Máximo de mortes +config.general.safety.max_deaths.desc=Quantidade máxima de mortes, se atingida, o bot estrará em pausa +config.general.safety.revive_location=Local de renacimento +config.general.safety.revive_location.desc=Onde reviver o nave +config.general.safety.revive_location.list.base=Base +config.general.safety.revive_location.list.portal=Portal +config.general.safety.revive_location.list.spot=Spot +config.general.safety.wait_before_revive=Aguarde antes de reviver (s) +config.general.safety.wait_before_revive.desc=Segundos para esperar antes de reviver +config.general.safety.wait_after_revive=Aguarde após reviver (s) +config.general.safety.wait_after_revive.des=Segundos para esperar após a revivificação, vamos enviar o reparo +# - Geral + Fugir. +config.general.running=Fugir +config.general.running.run_from_enemies=Fuja dos inimigos +config.general.running.run_from_enemies.desc=Se o bot for executado contra inimigos que o atacam, ou que recentemente o atacaram +config.general.running.remember_enemies_for=Lembre-se de inimigos para +config.general.running.remember_enemies_for.desc=Quanto tempo fugir à vista dos inimigos que atiraram em você +config.general.running.run_from_enemies_sight=Corra dos inimigos à vista +config.general.running.run_from_enemies_sight.desc=Corra dos inimigos assim que eles entrarem no seu minimapa +config.general.running.stop_running_no_sight=Pare de fugir quando estiver fora de vista +config.general.running.stop_running_no_sight.desc=Pare de fugir se o inimigo não estiver atacando e não estiver mais à vista +config.general.running.max_sight_distance=Distância máxima de visão +config.general.running.max_sight_distance.desc=Não considera mais inimigos à vista se estiver mais longe do que +config.general.running.ship_ability=Habilidade da nave +config.general.running.ship_ability.desc=Clicado ao fugir de um inimigo que atirou em você +config.general.running.ship_ability_min=Distância mínima da Habilidade da nave +config.general.running.ship_ability_min.desc=Distância mínima de segurança para usar a habilidade da nave +config.general.running.run_furthest_port=Distância máxima do portal mais próximo +config.general.running.run_furthest_port.desc=Fuja para portal mais longe do inimigo, a menos que portal esteja abaixo da distancia max +# - Geral + Comportamentos de viaje e area preferida +config.general.roaming=Percorrer e comportamento da área preferida +config.general.roaming.keep=Mantenha o percurso no mesmo ponto até ser alcançado +config.general.roaming.sequential=Percurso para zonas preferidas em ordem +config.general.roaming.only_kill_preferred=Mate apenas NPCs na área preferida +config.general.roaming.only_kill_preferred.desc=Selecione e mate NPCs apenas quando eles estiverem dentro de uma área preferida + +# - Coletar +config.collect=Coletar +config.collect.stay_away_from_enemies=Fique longe de inimigos +config.collect.auto_cloack=Camuflagem automática +config.collect.auto_cloack_key=Chave da camuflagem automática +config.collect.radius=Colete enquanto mata no alcanse de +config.collect.radius.desc=Raio da coleta de recursos enquanto mata NPCs +# - Colectar + TABELA +config.collect.box_table.resource=Recurso +config.collect.box_table.resource.desc=Caixas ou outros recursos que o bot viu +config.collect.box_table.collect=Coletar +config.collect.box_table.collect.desc=Se o recurso deve ser coletado +config.collect.box_table.wait=Aguardar (ms) +config.collect.box_table.wait.desc=Tempo de espera para pegar o recurso, em milissegundos +config.collect.box_table.priority=Prioridade +config.collect.box_table.priority.desc=A prioridade nº 1 será coletada antes da prioridade nº 2 + +# - Matar NPCs +config.loot=Matar NPC´s +config.loot.ammo_key=Chave de munição +config.loot.ship_ability=Chave de habilidade ofensiva +config.loot.ship_ability_min=Habilidade ofensiva min de saúde +config.loot.ship_ability_min.desc=Saúde mínima do NPC para usar a habilidade +config.loot.max_circle_iterations=Iterações em círculo inteligentes +config.loot.max_circle_iterations.desc=Maximo de giros para mudar a direção de rotação, 0 para desativar, recomendado 3-5 +config.loot.run_config_in_circle=Executar a configuração de perseguição +config.loot.run_config_in_circle.desc=Executar config para perseguir os npcs que escapam +config.loot.group_npcs=Agrupar NPCs similares +config.loot.group_npcs.desc=Agrupe NPCs no mesmo GG na tabela NPC +config.loot.npc_distance_ignore=Ignora npcs além de +# - Matar NPCs + SAB +config.loot.sab=Sab +config.loot.sab.desc=Sab npcs automáticos para sobreviver por mais tempo +config.loot.sab.enabled=Ativado +config.loot.sab.key=Chave +config.loot.sab.percent=Ativar sob +config.loot.sab.npc_amount=Escudo mínimo do NPC +# - Matar NPCs + RSB +config.loot.rsb=Rsb +config.loot.rsb.desc=Usa munição RSB em aliens com sinalizador UR +config.loot.rsb.enabled=Ativado +config.loot.rsb.key=Chave +config.loot.rsb.ammo_refresh=Recarga de Rsb + +# - Matar NPCs + TABELA +config.loot.npc_table.name=Nome +config.loot.npc_table.name.desc=Nomes NPC, npcs terminados em * são agrupados +config.loot.npc_table.radius=Raio +config.loot.npc_table.radius.desc=Distância a ser mantida neste npc +config.loot.npc_table.priority=Prioridade +config.loot.npc_table.priority.desc=A prioridade nº 1 será direcionada antes da prioridade nº 2 +config.loot.npc_table.kill=Matar +config.loot.npc_table.kill.desc=Se este npc deve ser morto +config.loot.npc_table.attack_key=Chave de munição +config.loot.npc_table.attack_key.desc=Munição especial para este npc, se desativado, o padrão é usado +config.loot.npc_table.attack_formation=Formação +config.loot.npc_table.attack_formation.desc=Formação especial para este npc, se não definido, a formação da configuração de ataque é usada +config.loot.npc_table.extra=Extra +config.loot.npc_table.extra.desc=Vários sinalizadores extras para o npc +# - Matar NPCs + TABELA + EXTRA´S +config.loot.npc_table.extra.no_circle=Sem rotação +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=Não circule pelo npc, apenas fique dentro do raio +config.loot.npc_table.extra.ignore_ownership=Ignorar propriedade +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Continue matando o npc mesmo se ele estiver sendo atacado por outro jogador +config.loot.npc_table.extra.ignore_attacked=Ignorar atacado +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Selecione o npc, mesmo que outros jogadores já estejam atirando nele +config.loot.npc_table.extra.passive=Passivo +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Seja passivo em relação a este npc, apenas atire se este npc estiver atir em você +config.loot.npc_table.extra.attack_second=Atacar segundo +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Atire apenas se outros já estiverem atacando.
Deve também selecionar ignorar atacado e ignorar propriedade +config.loot.npc_table.extra.use_rsb=Use rsb +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Use munição RSB para atacar este npc + +# - Pet +config.pet=Pet +config.pet.enabled=Usar P.E.T. +config.pet.enabled.desc=Se desativado, o bot ignorará todas as funções do P.E.T. +config.pet.module=Nº do módulo a ser usado +config.pet.module.desc= 0 - passivo
1 - Modo de guarda
2 - o que vier a seguir + +# - Diversos +config.miscellaneous=Diversos +config.miscellaneous.refresh_time=Atualize a cada +config.miscellaneous.refresh_time.desc=A cada quantos minutos para atualizar +config.miscellaneous.pause_for=Pausar após a atualização +config.miscellaneous.pause_for.desc=Quantos minutos de pausa após uma atualização +config.miscellaneous.drone_repair_percentage=Porcentagem de reparo de drone +config.miscellaneous.drone_repair_percentage.desc=Os drones são reparados se acima dessa porcentagem + +# - Ajustes do bot +config.bot_settings=Configurações do Bot +config.bot_settings.locale=Idioma +config.bot_settings.zone_resolution=Precisão da zona +config.bot_settings.zone_resolution.desc=Quantidade de subdivisões do mapa ao selecionar zonas +config.bot_settings.map_start_stop=Iniciar/pausar com um clique no mapa +config.bot_settings.map_start_stop.desc=Clique esquerdo para iniciar/pausar. Clique direito para mover a nave. +config.bot_settings.confirm_exit=Confirmar saída +config.bot_settings.confirm_exit.desc=Confirme antes de sair do bot +config.bot_settings.min_tick=Tempo mínimo de tick +config.bot_settings.dev_stuff=Mostrar material do desenvolvedor +config.bot_settings.dev_stuff.desc=Ativar isso fará com que seu bot use mais CPU. +# - Ajustes do bot + Mostrar +config.bot_settings.display=Exibir +config.bot_settings.display.show_names=Mostrar nomes de usuário +config.bot_settings.display.show_names.desc=Mostrar nomes de usuário no minimap +config.bot_settings.display.hide_name=Ocultar nome +config.bot_settings.display.hide_name.desc=Ocultar o nome do herói no mapa +config.bot_settings.display.trail_length=Comprimento da trilha +config.bot_settings.display.trail_length.desc=Quanto tempo a trilha deve ficar no mapa, em segundos +config.bot_settings.display.show_zones=Mostrar zonas no mapa principal +config.bot_settings.display.show_zones.desc=Ative para mostrar zonas preferidas / evitadas no mapa +config.bot_settings.display.button_size=Tamanho do botão GUI +config.bot_settings.display.button_size.desc=Alterar aba na janela principal de configuração e redimensionar para atualizar +config.bot_settings.display.hide_editors=Ocultar editores de configuração até clicar +config.bot_settings.display.hide_editors.desc=Use a árvore de configuração baseada em texto, que consome menos recursos + +# Locais de segurança +# - Lugares +safety_places.type.portal=Portal +safety_places.type.cbs=CBS +safety_places.type.base=Base +# - [Generico] +safety_places.run_mode=[Todos] Condição de uso +safety_places.run_mode.always=Sempre +safety_places.run_mode.enemy_flee_only=Fugindo (Inimigo à vista) +safety_places.run_mode.repair_only=Reparando (sem inimigos) +safety_places.run_mode.refresh=Atualizar +safety_places.run_mode.never=Nunca +# - [Portal] +safety_places.jump_mode=[Portal] Condição do salto +safety_places.jump_mode.never=Nunca +safety_places.jump_mode.escaping=Escapando (tiro inimigo) +safety_places.jump_mode.fleeing=Fugindo (inimigo à vista) +safety_places.jump_mode.repairing=Reparando +safety_places.jump_mode.always=Sempre +safety_places.jump_mode.always_other_side=Sempre (repare no outro lado) +# - [CBS] +safety_places.cbs_mode=[CBS] Usar condição +safety_places.cbs_mode.ally=Aliado da CBS +safety_places.cbs_mode.ally_neutral=Aliado ou vazio + +# Plugins +plugins.reload=Recarregar plugins +plugins.config_button.enabled.desc=Mostrar configuração +plugins.config_button.disabled.desc=Configuração desativada, recurso não carregado +plugins.config_button.popup=Não foi possível editar a configuração +plugins.config_button.popup.desc=A configuração não está disponível para o recurso descarregado \nnAtive o recurso, carregue-o e tente novamente. +plugins.issues.invalid_json=plugin.json inválido +plugins.issues.invalid_json.desc=A versão mínima {0} é superior à versão suportada {1} +plugins.issues.bot_update=Atualização do bot necessária +plugins.issues.bot_update.desc=Este plugin requer {0}, portanto, não pode ser executado no DarkBot v {1} +plugins.issues.plugin_update=O plug-in pode precisar de atualização +plugins.issues.plugin_update.desc=O plug-in foi criado para {0}, portanto, pode não funcionar no DarkBot v {1} +plugins.issues.signature.plugin_not_signed=Plugin não assinado +plugins.issues.signature.plugin_not_signed.desc=Este plugin não foi assinado ou tem uma assinatura inválida +plugins.issues.signature.unknown_signature=Assinatura desconhecida +plugins.issues.signature.unknown_signature.desc=Este plugin foi assinado com um certificado não confiável +plugins.issues.signature.invalid_signature=Assinatura inválida +plugins.issues.signature.invalid_signature.desc=O plugin possui uma assinatura inválida ou foi adulterado + +# GUI principal +gui.hamburger_button.home=HOME +gui.hamburger_button.reload=RELOGAR +gui.hamburger_button.copy_sid=COPIAR SID +gui.hamburger_button.discord=DISCORD +gui.config_button=Mostrar /ocultar configuração +gui.start_button=Iniciar/Pausar bot +gui.visibility_button=Mostrar/ocultar o navegador +gui.pin_button=Sempre no topo + +gui.exit=Tem certeza de que deseja sair? +gui.exit.desc=Confirme a saída do bot, pode ser desativado em configurações> ~configurações do bot +gui.exit.yes_button=Sim +gui.exit.cancel_button=Cancelar + +gui.map.info=v{0} - Refresh: {1}/{2} +gui.map.loading=Carregando +gui.map.waiting=AGUARDANDO {0} +gui.map.running=RODANDO A {0} + +# Modulos +module.instructions.title=Instruções +# - Modulos de estado + Estadistica +module.kill_n_collect.status=Matar: {0} - Coletar: {1} + +module.map_travel.status.has_next=Viajando para {0}, próximo mapa: {1} +module.map_travel.status.no_next=Viajando para {0} +# - Modulos de estado + Conexion +module.disconnect.status=Desconectando ({0}): {1} +module.disconnect.status_stopped=Desconectado ({0}) +module.disconnect.status_paused=Pausado ({0}), reconectando em {1} +module.disconnect.reason.break=fazer uma pausa +module.disconnect.reason.honor=honra perdida + +# Problemas +bot.issue.module_load_failed=Falha ao carregar o módulo {0}, usando o padrão +bot.issue.config_load_failed=Falha ao carregar o arquivo de configuração. \nO bot usará a configuração padrão e não salvará. \nExclua o arquivo config.json (apagará as configurações) e reiniciará o bot. +bot.issue.feature.failed_to_tick=Falha ao marcar +bot.issue.feature.failed_to_load=Falha ao carregar o recurso +bot.issue.feature.failed_to_load.desc={0} não pôde ser registrado corretamente: {1} + +# Verificador +# - Verificador + discord +verifier.discord_warn.title=Junte-se à discórdia oficial! +verifier.discord_warn.content.1=Este bot é gratuito, se você pagou por ele ou assistiu a anúncios, você foi enganado! +verifier.discord_warn.content.2=Verifique se você está no servidor oficial da discord para obter as atualizações mais recentes de graça. +verifier.discord_warn.checkbox.dont_show=Não mostrar esta mensagem novamente +verifier.discord_warn.button.join_discord=Ingressar na discórdia +verifier.discord_warn.button.ignore=Ignorar +# - Verificador + authenticate +verifier.authenticate.error=O bot falhou ao iniciar. Tente reiniciar o seu PC. +verifier.authenticate.title=Autenticação de discórdia +verifier.authenticate.content=Siga as instruções no seu navegador, não feche esta janela. \nA autenticação é local e não envia dados para nenhum servidor. \nSe a página não carregar no seu navegador, tente usar o Chrome ou desativando antivírus. +# - Verificador + panel +verifier.panel.invalid_auth=Autenticação inválida, tente reiniciar o bot +verifier.panel.authenticated.no_server=Você foi autenticado como {0}
Você não parece estar em nenhum servidor. +verifier.panel.authenticated.servers=Você foi autenticado como {0}, membro de:
    {1}
+verifier.panel.authenticated.join_official=Você deve ingressar no servidor oficial para obter atualizações!
+verifier.panel.authenticated.blacklisted=Você é membro de servidores na lista negra que estão redistribuindo meu trabalho sem o meu consentimento.
Você deve deixar os servidores na lista negra antes de usar o bot. Clique para atualizar depois de sair.
+ +# Idioma alterado +language.changed.title=O idioma do bot foi alterado +language.changed.content=O bot agora está usando uma tradução para {0} criada por {1}.\nÉ recomendável que você reinicie o bot para aplicar completamente as alterações. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@Douglas#3507 & @BR/PT-💻 ☣DARK☠EVIL☣#2328 \ No newline at end of file diff --git a/src/main/resources/lang/strings_ro.properties b/src/main/resources/lang/strings_ro.properties new file mode 100644 index 00000000..505a21c1 --- /dev/null +++ b/src/main/resources/lang/strings_ro.properties @@ -0,0 +1,115 @@ +config.general=General +config.general.current_module=Modul +config.general.working_map=Hartă de lucru +config.general.offensive=Configurația agresivă +config.general.offensive.desc=Folosită pentru a omorâ NPC-uri +config.general.roam=Configurația de deplasare +config.general.roam.desc=Folosită pentru a parcurge harta, căutând NPC-uri +config.general.run=Configurația de fugă +config.general.run.desc=Folosită pentru a fugi la zonele sigure sau schimbe hărțile +config.general.formation_check=Verifică formația +config.general.formation_check.desc=Verifică formația fiecare X secunde, setează 0 pentru a dezactiva +config.general.safety=Siguranță +config.general.safety.repair_hp_range=Păstrează viața între +config.general.safety.repair_hp_range.desc=Chiar dacă tragi într-un NPC, fugi dacă ești sub nivelul de viață respectiv +config.general.safety.repair_hp_no_npc=Reparare când te deplasezi & viață sub +config.general.safety.repair_hp_no_npc.desc=Viața sub care să fugi dacă te deplasezi pe hartă +config.general.safety.repair_to_shield=Repară scutul până +config.general.safety.repair_to_shield.desc=Scutul minim până la oprirea reparării +config.general.safety.repair=Configurația de reparat +config.general.safety.repair.desc=Folosită pentru reparat după ce scuturile configurației de fugit sunt pline +config.general.safety.max_deaths=Morți maxime +config.general.safety.max_deaths.desc=Cantitatea maximă de morți, odată ajunsă, botul se oprește +config.general.safety.revive_location=Locația de înviere +config.general.safety.revive_location.desc=Unde să reînvii nava +config.general.safety.wait_before_revive=Așteaptă înainte de înviere (secunde) +config.general.safety.wait_before_revive.desc=Secunde de așteptat înainte de înviere +config.general.safety.wait_after_revive=Așteaptă după înviere (secunde) +config.general.safety.wait_after_revive.des=Secunde de așteptat după înviere, lasă nava să se repare +config.general.running=Alergare +config.general.running.run_from_enemies=Fugi de inamici +config.general.running.run_from_enemies.desc=Dacă botul ar trebui să fugă de inamicii care atacă, sau te-au atacat recent +config.general.running.remember_enemies_for=Ține minte inamicii pentru +config.general.running.remember_enemies_for.desc=Cat timp să țină minte inamicii care te-au atacat +config.general.running.run_from_enemies_sight=Fugi de inamicii din apropiere +config.general.running.run_from_enemies_sight.desc=Fugi de inamici de îndată ce apar pe harta mini +config.general.running.stop_running_no_sight=Nu mai fugi când inamicul este în afara razei +config.general.running.stop_running_no_sight.desc=Se va opri din fugit dacă inamicul nu mai atacă și nu mai este în raza respectivă +config.general.running.max_sight_distance=Rază maximă de vedere +config.general.running.max_sight_distance.desc=Nu mai consideră jucătorii inamici dacă sunt mai departe de distanța respectivă +config.general.running.ship_ability=Abilitatea navei +config.general.running.ship_ability.desc=Activată când fugi de un inamic care te atacă +config.general.running.ship_ability_min=Abilitatea navei distanță minimă +config.general.running.ship_ability_min.desc=Distanța minimă de portal pentru a folosi abilitatea +config.general.running.run_furthest_port=Distanța maximă pentru cel mai apropiat portal +config.general.running.run_furthest_port.desc=Fuge la cel mai depărtat portal de inamici, dacă portalul nu este sub distanța maximă +config.general.roaming=Călătorie & Comportamentul zonelor preferate +config.general.roaming.keep=Continuă să mergi către același punct până când ai ajuns +config.general.roaming.sequential=Mergi către zonele preferate în ordine +config.general.roaming.only_kill_preferred=Omoară doar NPC-urile din zonele preferate +config.general.roaming.only_kill_preferred.desc=Selecteaza și omoară NPC-urile doar când sunt in zonele preferate + +config.collect=Colectează +config.collect.stay_away_from_enemies=Stai departe de inamici +config.collect.auto_cloack=Camumflare automată +config.collect.auto_cloack_key=Tastă camumflare automată +config.collect.radius=Raza colectării în timp ce omori +config.collect.radius.desc=Rază de colectare a resurselor în timp ce omori NPC-uri + +config.loot=Ucideri NPC +config.loot.sab=Sab +config.loot.sab.desc=Folosește sab pe npc-uri pentru a supraviețui mai mult +config.loot.sab.enabled=Activat +config.loot.sab.key=Tastă +config.loot.sab.percent=Nava sub +config.loot.sab.npc_amount=Scut minim NPC +config.loot.ammo_key=Tasta muniției +config.loot.ship_ability=Tasta abilității ofensive +config.loot.ship_ability_min=Viață minimă abilitate ofensivă +config.loot.ship_ability_min.desc=Viața minimă a NPC-ului pentru a folosi abilitatea +config.loot.max_circle_iterations=Rotire inteligentă +config.loot.max_circle_iterations.desc=Număr maxim de mișcări înaintate până la schimbarea direcției rotirii, 0 pentru dezactivare, recomandat 3-5 +config.loot.run_config_in_circle=Configurația de fugă pentru urmărit +config.loot.run_config_in_circle.desc=Foloșeste configurația de fuga pentru a urmari NPC-urile care încearcă să scape +config.loot.group_npcs=Grupează NPC-urile similare +config.loot.group_npcs.desc=Grupează NPC-urile din aceeași poartă în tabelul de NPC-uri +config.loot.npc_distance_ignore=Ignoră NPC-urile mai depărtate de + +config.pet=Pet +config.pet.enabled=Folosește petul +config.pet.enabled.desc=Odată dezactivat, botul va ignora toate functiile petului +config.pet.module=# modulului folosit +config.pet.module.desc=0 - pasiv
1 - Modul de protecție
2 - Orice urmează + +config.miscellaneous=Diverse +config.miscellaneous.refresh_time=Reînprospătare fiecare +config.miscellaneous.refresh_time.desc=La câte minute să reînprospătezi +config.miscellaneous.pause_for=Pauză după reînprospătare +config.miscellaneous.pause_for.desc=Pentru câte minute botul să aștepte după o reînprospătare +config.miscellaneous.drone_repair_percentage=Procentaj reparare drone +config.miscellaneous.drone_repair_percentage.desc=Dronele sunt reparate dacă daunele sunt peste acest procentaj + +config.bot_settings=Setarile botului +config.bot_settings.locale=Limbă +config.bot_settings.zone_resolution=Precizia zonei +config.bot_settings.zone_resolution.desc=Valoarea subdiviziunilor hărții la selectarea zonelor +config.bot_settings.map_start_stop=Pornește/oprește apasând pe hartă +config.bot_settings.map_start_stop.desc=Click stânga pentru pornire/oprire. Click dreapta ca să muți nava +config.bot_settings.confirm_exit=Confirmare ieșire +config.bot_settings.confirm_exit.desc=Confirmă înainte să închizi botul +config.bot_settings.min_tick=Timpul minim dintre acțiuni +config.bot_settings.dev_stuff=Arată lucrurile dezvoltatorului +config.bot_settings.dev_stuff.desc=Activând asta va face botul să consume mai mult procesor +config.bot_settings.display=Afișare +config.bot_settings.display.show_names=Arată numele +config.bot_settings.display.show_names.desc=Arată numele pe hartă +config.bot_settings.display.hide_name=Ascunde numele +config.bot_settings.display.hide_name.desc=Ascunde numele pe hartă +config.bot_settings.display.trail_length=Lungimea cozii +config.bot_settings.display.trail_length.desc=Cantitatea de timp în care coada va rămâne pe hartă, în secunde +config.bot_settings.display.show_zones=Arată zonele pe harta principală +config.bot_settings.display.show_zones.desc=Activează pentru a arăta zonele evitate/preferate pe hartă +config.bot_settings.display.button_size=Mărimea butonului GUI +config.bot_settings.display.button_size.desc=Schimbă butonul în config & redimensionează fereastra principală + +translation.credit=@Mariusel#4225 \ No newline at end of file diff --git a/src/main/resources/lang/strings_ru.properties b/src/main/resources/lang/strings_ru.properties new file mode 100644 index 00000000..3fdffb49 --- /dev/null +++ b/src/main/resources/lang/strings_ru.properties @@ -0,0 +1,342 @@ +# Config tabs +tabs.general=Главная +tabs.general.desc=Общие настройки бота +tabs.zones=Зоны карты +tabs.zones.desc=Выберите предпочтительные, избегаемые и настройки зоны безопасности +tabs.preferred_zones=Предпочтительные зоны +tabs.preferred_zones.desc=Зоны, в которые бот будет случайно перемещаться +tabs.avoided_zones=Запретные зоны +tabs.avoided_zones.desc=Зоны рассматриваются ботом как барьеры +tabs.safety_places=Безопасные места +tabs.safety_places.desc=Безопасные места, куда можно бежать, чтобы починить или убежать от врага +tabs.players=Игроки +tabs.players.desc=Вкладка «Игрок» для управления другими игроками или учетными записями темной орбиты +tabs.plugins=Плагины +tabs.plugins.desc=Информация и конфиги для установленных плагинов + +# Config tree +config.general=Главная +config.general.current_module=Модуль +config.general.working_map=Использовать карту +config.general.offensive=Конфигурация атаки +config.general.offensive.desc=Использовать формацию для убийства NPC +config.general.roam=Конфигурация поиска +config.general.roam.desc=Использовать формацию для перемещения по карте и поиска NPC +config.general.run=Беговая конфигурация +config.general.run.desc=Использовать формацию для ухода от врагов или при смене карты +config.general.formation_check=Проверка формаций +config.general.formation_check.desc=Проверять формации каждые X секунд, введите 0 для отключения +config.general.safety=Безопасноть +config.general.safety.repair_hp_range=Сохранять прочность в интервале +config.general.safety.repair_hp_range.desc=Убегает, даже если стреляете в NPC +config.general.safety.repair_hp_no_npc=Ремонт при перемещении, когда прочность ниже +config.general.safety.repair_hp_no_npc.desc=Кол-во прочности для ухода даже когда перемещаетесь по карте +config.general.safety.repair_to_shield=Мин. кол-во щита +config.general.safety.repair_to_shield.desc=Минимальное кол-во щита для прекращения ремонта +config.general.safety.repair=Ремонтный конфиг +config.general.safety.repair.desc=Используется для ремонта, когда щит ремонтной формации полон +config.general.safety.max_deaths=Макс. смертей +config.general.safety.max_deaths.desc=Максимальное количество смертей до остановки бота +config.general.safety.revive_location=Место возрождения +config.general.safety.revive_location.desc=Где возродить корабль +config.general.safety.revive_location.list.base=База +config.general.safety.revive_location.list.portal=Порт +config.general.safety.revive_location.list.spot=На месте +config.general.safety.wait_before_revive=Ожидание между возрождениями (сек) +config.general.safety.wait_before_revive.desc=Ожидание до возрождения в секундах +config.general.safety.wait_after_revive=Ожидание после возрождения (сек) +config.general.safety.wait_after_revive.des=Ожидание после возрождения в секундах +config.general.running=Бег +config.general.running.run_from_enemies=Убегать от врагов +config.general.running.run_from_enemies.desc=Убегать от врагов, которые атакуют либо недавно атаковали +config.general.running.remember_enemies_for=Запоминание врагов +config.general.running.remember_enemies_for.desc=Как долго убегать от врагов, которые стреляли в тебя +config.general.running.run_from_enemies_sight=Убегать от врагов в поле зрения +config.general.running.run_from_enemies_sight.desc=Убегать от врагов, которые видны на миникарте +config.general.running.stop_running_no_sight=Прекращает убегать, когда скрыты из виду +config.general.running.stop_running_no_sight.desc=Перестает убегать если враг не атакует и пропал из виду +config.general.running.max_sight_distance=Максимальное поле зрения +config.general.running.max_sight_distance.desc=Не рассматривает врагов, если они находятся дальше +config.general.running.ship_ability=Способность корабля +config.general.running.ship_ability.desc=Нажимает, когда убегает от врага +config.general.running.ship_ability_min=Мин. дистанция для способности +config.general.running.ship_ability_min.desc=Минимальная дистанция для применения способности +config.general.running.run_furthest_port=Макс. расстояние до ближайшего порта +config.general.running.run_furthest_port.desc=Убегает в порт подальше от врага, если порт не дальше +config.general.roaming=Поведение при перемещении и предпочитаемые зоны +config.general.roaming.keep=Продолжает двигаться в направлении той же точки, пока не достигнет +config.general.roaming.sequential=Перемещаться по предпочитаемым зонам +config.general.roaming.only_kill_preferred=Убивать NPCs в предпочитаемой зоне +config.general.roaming.only_kill_preferred.desc=Убивать NPCs только когда они в предпочитаемой зоне + +config.collect=Сбор +config.collect.stay_away_from_enemies=Держаться подальше от врагов +config.collect.auto_cloack=Автоиспользование невидимости +config.collect.auto_cloack_key=Кнопка использования невидимости +config.collect.radius=Собирать, когда радиус убийства +config.collect.radius.desc=Радиус сбора ресурсов при убийстве NPC + +config.collect.box_table.resource=Ресурс +config.collect.box_table.resource.desc=Коробки или другие ресурсы, которые бот видел +config.collect.box_table.collect=Сбор +config.collect.box_table.collect.desc=Если ресурс должен быть собран +config.collect.box_table.wait=Ожидание (ms) +config.collect.box_table.wait.desc=Время ожидания, чтобы забрать ресурс, в миллисекундах +config.collect.box_table.priority=Приоритет +config.collect.box_table.priority.desc=Приоритет № 1 будет получен до приоритета № 2 + +config.loot=Убийство NPC +config.loot.sab=Саб +config.loot.sab.desc=Использование Саб, NPC живет дольше +config.loot.sab.enabled=Включено +config.loot.sab.key=Кнопка +config.loot.sab.percent=Щит ниже +config.loot.sab.npc_amount=Мин. кол-во щита NPC +config.loot.rsb=Рсб +config.loot.rsb.desc=Использовать боеприпасы РСБ для NPC +config.loot.rsb.enabled=Включено +config.loot.rsb.key=Кнопка +config.loot.rsb.ammo_refresh=Время перезарядки Рсб(млс) +config.loot.ammo_key=Кнопка боеприпасов +config.loot.ship_ability=Кнопка использование навыка корабля +config.loot.ship_ability_min=Минимальная прочность для использование навыка +config.loot.ship_ability_min.desc=Мин. здоровье NPC для использование навыка +config.loot.max_circle_iterations=Умные круговые вращения +config.loot.max_circle_iterations.desc=Макс. кол-во движений для изменение направления круга. 0 отключить, рекомендуется 3-5 +config.loot.run_config_in_circle=Беговой кофиг для преследования +config.loot.run_config_in_circle.desc=Использовать беговой конфиг для преследования NPC +config.loot.group_npcs=Группа похожих NPC +config.loot.group_npcs.desc=Группировать NPC в одном GG в таблице NPC +config.loot.npc_distance_ignore=Игнорировать NPC дальше чем + +config.loot.npc_table.name=Имена +config.loot.npc_table.name.desc=Имена NPC, заканчивающиеся на * NPC сгруппированы +config.loot.npc_table.radius=Радиус +config.loot.npc_table.radius.desc=Расстояние от этого NPC +config.loot.npc_table.priority=Приоритет +config.loot.npc_table.priority.desc=Приоритет № 1 будет нацелен на приоритет № 2 +config.loot.npc_table.kill=Убийство +config.loot.npc_table.kill.desc=Если этот NPC должен быть убит +config.loot.npc_table.attack_key=Боеприпасы +config.loot.npc_table.attack_key.desc=Специальные боеприпасы для этого NPC, если не установлено, используется по умолчанию +config.loot.npc_table.attack_formation=Формация +config.loot.npc_table.attack_formation.desc=Специальная формация для этого NPC, если не установлено, использовать формацию из конфигурации атаки +config.loot.npc_table.extra=Дополнительный +config.loot.npc_table.extra.desc=Несколько дополнительных флагов для NPC + +config.loot.npc_table.extra.no_circle=Нет круга +config.loot.npc_table.extra.no_circle.short=НК +config.loot.npc_table.extra.no_circle.desc=Не обводите NPC, просто оставайтесь в радиусе +config.loot.npc_table.extra.ignore_ownership=Игнорировать право собственности +config.loot.npc_table.extra.ignore_ownership.short=ИС +config.loot.npc_table.extra.ignore_ownership.desc=Продолжайте убивать NPC, даже если у него есть белый замок +config.loot.npc_table.extra.ignore_attacked=Игнорировать если атаковали +config.loot.npc_table.extra.ignore_attacked.short=ИA +config.loot.npc_table.extra.ignore_attacked.desc=Выберите NPC, даже если другие игроки уже стреляют в него +config.loot.npc_table.extra.passive=Пассивный +config.loot.npc_table.extra.passive.short=П +config.loot.npc_table.extra.passive.desc=Будьте пассивны по отношению к этому NPC, стреляйте только если NPC стреляет в вас +config.loot.npc_table.extra.attack_second=Вторая атака +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Стреляйте, только если другие уже атакуют.
Должен также выберите игнорировать атакуемых и игнорировать владение +config.loot.npc_table.extra.use_rsb=Использовать rsb +config.loot.npc_table.extra.use_rsb.short=РСБ +config.loot.npc_table.extra.use_rsb.desc=Использовать боеприпасы RSB, чтобы атаковать этот NPC +config.loot.npc_table.extra.pet_locator=Пет локатор +config.loot.npc_table.extra.pet_locator.short=ПЛ +config.loot.npc_table.extra.pet_locator.desc=Используйте питомца, чтобы найти NPC + +config.pet=ПET +config.pet.enabled=Использовать ПET +config.pet.enabled.desc=Если отключено, бот игнорирует все ПET функции +config.pet.module_id=Использовать режим +config.pet.module.desc=Список обновится до ваших модулей, если ваш питомец включен + +config.group=Группа +config.group.accept_invites=Принимать приглашения +config.group.accept_invites.desc=Бот принимает приглашения от игроков +config.group.whitelist_tag=Принимать приглашения от +config.group.whitelist_tag.desc=Принимайте приглашения только от пользователей из белого списка +config.group.invite_tag=Отправить приглашения на +config.group.invite_tag.desc=Отправить приглашения игрокам с этим тегом +config.group.open_invites=Открытые приглашения (Лидер) +config.group.open_invites.desc=Разрешить любому приглашать других +config.group.kick_tag=Кик игроков (Лидер) +config.group.kick_tag.desc=Кик игроков из группы + +config.miscellaneous=Прочее +config.miscellaneous.refresh_time=Обновлять каждые +config.miscellaneous.refresh_time.desc=Как часто нужно обновлять в минутах +config.miscellaneous.pause_for=Отдых после обновления +config.miscellaneous.pause_for.desc=Сколько минут требуется отдыхать после обновления +config.miscellaneous.drone_repair_percentage=Процент починки дройдов +config.miscellaneous.drone_repair_percentage.desc=Дроны ремонтируются, если урон выше данного значения +config.miscellaneous.log_chat=Написать сообщения чата в файл журнала + +config.bot_settings=Настройки бота +config.bot_settings.locale=Языки +config.bot_settings.force_game_language=Принудительный внутриигровый язык +config.bot_settings.force_game_language.desc=Установите язык в игре на язык бота +config.bot_settings.zone_resolution=Точность зоны +config.bot_settings.zone_resolution.desc=Размер сетки при выделении зоны +config.bot_settings.map_start_stop=Кликать старт/стоп на карте +config.bot_settings.map_start_stop.desc=Левый клик на старт/стоп. Правый клик для перемещения корабля +config.bot_settings.confirm_exit=Подтверждение выхода +config.bot_settings.confirm_exit.desc=Требовать подтверждения для выхода с бота +config.bot_settings.min_tick=Минимальное время тика +config.bot_settings.dev_stuff=Включить режим разработчика +config.bot_settings.dev_stuff.desc=При включении увеличивается нагрузка на CPU +config.bot_settings.display=Отображение +config.bot_settings.display.show_names=Отображение никнеймов +config.bot_settings.display.show_names.desc=Показывать никнеймы на миникарте +config.bot_settings.display.hide_name=Скрывать никнейм +config.bot_settings.display.hide_name.desc=Скрывать никнейм на карте +config.bot_settings.display.trail_length=Длина следа +config.bot_settings.display.trail_length.desc=Время, в течение которого след должен быть на карте (сек) +config.bot_settings.display.hide_group_names=Скрыть названия групп +config.bot_settings.display.hide_group_names.desc=Скрыть имена членов группы на карте +config.bot_settings.display.show_zones=Показывать зоны на карте +config.bot_settings.display.show_zones.desc=Позволяет показывать зоны, которые вы избегали / предпочитали +config.bot_settings.display.button_size=GUI размер кнопок +config.bot_settings.display.button_size.desc=Измените вкладку в конфигурации и измените размер главного окна, чтобы обновить + +# Safety places +safety_places.type.portal=Порт +safety_places.type.cbs=Клановая база +safety_places.type.base=База + +safety_places.run_mode= [Все] Используйте условие +safety_places.run_mode.always=Всегда +safety_places.run_mode.enemy_flee_only=Бежать когда (Враг на виду) +safety_places.run_mode.repair_only=Ремонт (Нет врагов) +safety_places.run_mode.refresh=Обновление +safety_places.run_mode.never=Никогда + +safety_places.jump_mode= [Портал] Условие прыжка +safety_places.jump_mode.never=Никогда +safety_places.jump_mode.escaping=Бежать когда (Враг стреляет по Вам) +safety_places.jump_mode.fleeing=Бежать когда (Враг на виду) +safety_places.jump_mode.repairing=Ремонт +safety_places.jump_mode.always=Всегда +safety_places.jump_mode.always_other_side=Всегда (Ремонт на другой стороне) + +safety_places.cbs_mode= [Клановая база] Использовать условие +safety_places.cbs_mode.ally=Союзная база +safety_places.cbs_mode.ally_neutral=Союзная база или пустой камень + +#Players +players.add_player=Добавить игрока +players.add_player.by_name=Пользователь по имени +players.add_player.by_id=Пользователь по идентификатору +players.add_player.username=Имя пользователя: +players.add_player.user_id=ID пользователя: +players.add_player.invalid_id=Неверный идентификатор пользователя +players.add_player.invalid_id.no_number={0} не является допустимым номером +players.remove_player.title=Ты уверен? +players.remove_player.msg=Вы хотите удалить выбранных игроков {0}? + +players.tag_default.unset=снята с охраны +players.tag_default.all=Все +players.tag_default.none=Никто +players.select_players.warn.title=Сначала выберите игроков +players.select_players.warn.add=Выберите игроков, для которых вы хотите добавить тег +players.select_players.warn.remove=Выберите игроков, из которых вы хотите удалить тег +players.select_players.warn.remove_player=Выберите игроков, которых вы хотите удалить +players.delete_tag.confirm.title=Вы уверены? +players.delete_tag.confirm.msg=Вы хотите удалить тег {0} и удалить его у всех? + +# Plugins +plugins.reload=Перезагрузить плагины +plugins.config_button.enabled.desc=Показать конфигурацию +plugins.config_button.disabled.desc=Конфиг отключен, функция не загружена +plugins.config_button.popup=Не могу отредактировать конфиг +plugins.config_button.popup.desc=Конфигурация недоступна для выгруженной функции \nВключите функцию и загрузите ее, затем повторите попытку. + +plugins.issues.invalid_json=Неверный плагин.json +plugins.issues.invalid_json.desc=Минимальная версия {0} выше, чем поддерживаемая версия {1} +plugins.issues.bot_update=Требуется обновление бота +plugins.issues.bot_update.desc=Этот плагин требует {0}, поэтому он не может работать на DarkBot v {1} +plugins.issues.loaded_twice=Уже загруженный плагин +plugins.issues.loaded_twice.desc=Плагин с таким именем уже загружен +plugins.issues.plugin_update=Плагину может потребоваться обновление +plugins.issues.plugin_update.desc=Плагин создан для {0}, поэтому он может не работать на DarkBot v {1} + +plugins.issues.signature.plugin_not_signed=Плагин не подписан +plugins.issues.signature.plugin_not_signed.desc=Этот плагин не был подписан или имеет неверную подпись +plugins.issues.signature.unknown_signature=Неизвестная подпись +plugins.issues.signature.unknown_signature.desc=Этот плагин был подписан с ненадежным сертификатом +plugins.issues.signature.invalid_signature=Неверная подпись +plugins.issues.signature.invalid_signature.desc=Плагин имеет неверную подпись или был подделан + +# Main GUI +gui.hamburger_button.home=Открыть главную стр в браузере +gui.hamburger_button.reload=Обновить игр. стр. бота +gui.hamburger_button.copy_sid=Копия SID +gui.hamburger_button.discord=Дискорд +gui.config_button=Показать / Скрыть конфигурацию +gui.start_button=Старт / Стоп +gui.visibility_button=Показать / Скрыть браузер +gui.pin_button=Поверх всех окон(вкл/выкл) + +gui.exit=Вы уверены, что хотите выйти? +gui.exit.desc=Подтвердить выход из бота, можно отключить в настройках> настройки бота +gui.exit.yes_button=Да +gui.exit.cancel_button=Отмена + +gui.map.info=v{0} - Перезагрузка: {1}/{2} +gui.map.loading=Загрузка +gui.map.waiting=Ожидание {0} +gui.map.running=В полёте☺ {0} + +# Modules +module.instructions.title=инструкции +module.kill_n_collect.status=Убить: {0} - собрать: {1} + +module.map_travel.status.has_next=Путешествие к {0}, следующая карта: {1} +module.map_travel.status.no_next=Путешествие в {0} + +module.disconnect.status=Отключение ({0}): {1} +module.disconnect.status_stopped=Отключено ({0}) +module.disconnect.status_paused=Приостановлено ({0}), переподключение к {1} +module.disconnect.reason.break=перерыв +module.disconnect.reason.honor=потерянная честь +module.disconnect.reason.draw_fire=Враг использовал огонь + +# Players +gui.players.not_found.title=Пользователь не найден +gui.players.not_found.by_name=Не удалось найти пользователя с именем {0}. \nNПопробуйте добавить игрока по идентификатору пользователя +gui.players.not_found.by_id=Не удалось найти пользователя с идентификатором {0}.\nNУбедитесь, что он принадлежит игроку на вашем сервере + +# Issues +bot.issue.module_load_failed=Не удалось загрузить модуль {0}, используя значение по умолчанию +bot.issue.config_load_failed=Не удалось загрузить файл конфигурации.\nБот будет использовать конфигурацию по умолчанию и не сохранит.\nУдалите файл config.json (удалит настройки) и перезапустите бот. +bot.issue.feature.failed_to_tick=Не удалось загрузить функции +bot.issue.feature.failed_to_load=Не удалось загрузить функцию +bot.issue.feature.failed_to_load.desc={0} не может быть правильно зарегистрирован: {1} + +bot.issue.resolution=Измените разрешение на свой страх и риск!
Если у вас возникнут проблемы, установите значение по умолчанию! + +# Verifier +verifier.discord_warn.title=Присоединяйтесь к официальному серверу! +verifier.discord_warn.content.1=Этот бот бесплатный, если вы заплатили за него или смотрели рекламу, вас обманули! +verifier.discord_warn.content.2=Убедитесь, что вы находитесь на официальном сервере, чтобы получать последние обновления бесплатно. +verifier.discord_warn.checkbox.dont_show=Больше не показывать это сообщение +verifier.discord_warn.button.join_discord=Присоединяйся к серверу +verifier.discord_warn.button.ignore=игнорировать + +verifier.authenticate.error=Бот не запустился. Попробуйте перезагрузить компьютер. +verifier.authenticate.title=Дискорд аутентифицировать +verifier.authenticate.content=Следуйте инструкциям в вашем браузере, не закрывайте это окно.\nАутентификация является локальной и не отправляет никаких данных на любой сервер.\nЕсли страница не загружается в вашем браузере, попробуйте использовать Chrome или отключить антивирус. + +verifier.panel.invalid_auth=Неправильная аутентификация, попробуйте перезапустить бот +verifier.panel.authenticated.no_server=Вы были аутентифицированы как {0}
Вы, похоже, не находитесь ни на одном сервере. +verifier.panel.authenticated.servers=Вы были аутентифицированы как {0}, участник:
    {1} +verifier.panel.authenticated.join_official=Чтобы получать обновления, вам необходимо присоединиться к официальному серверу !
    +verifier.panel.authenticated.blacklisted=Вы являетесь участником черных серверов, которые перераспределяют мою работу без моего согласия.
    Вы должны покинуть черные списки серверов перед использованием бота. Нажмите, чтобы обновить после того, как вы ушли.
    " + +# Language changed +language.changed.title=Язык бота изменился +language.changed.content=Теперь бот использует перевод в {0}, созданный {1}.\nрекомендуется перезапустить бот, чтобы полностью применить изменения. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@Nikson#2036 \ No newline at end of file diff --git a/src/main/resources/lang/strings_tr.properties b/src/main/resources/lang/strings_tr.properties new file mode 100644 index 00000000..61796e6b --- /dev/null +++ b/src/main/resources/lang/strings_tr.properties @@ -0,0 +1,339 @@ +# Config tabs +tabs.general=Genel +tabs.general.desc=Bot hakkındaki genel ayarlar +tabs.zones=Harita Alanları +tabs.zones.desc=Kaçınılması Gereken Ve Güvenli Olan Alanlarınızı Seçin +tabs.preferred_zones=Tercih Edilen Alanlar +tabs.preferred_zones.desc=Botun öncelikli olarak gezineceği alanlar +tabs.avoided_zones=Kaçınılan Alanlar +tabs.avoided_zones.desc=Botun girmekten kaçınacağı alanlar +tabs.safety_places=Güvenli Alanlar +tabs.safety_places.desc=Tamir ve kaçış için tercih edilen, güvenli alanlar +tabs.players=Kullanıcı +tabs.players.desc=Diğer Oyuncuları Veya Hesapları Yapılandırmak İçin Oynatıcı Bölümü +tabs.plugins=Eklentiler +tabs.plugins.desc=Yüklü eklentiler için verilmiş bilgi ve ayarlar + +# Config tree +config.general=Genel +config.general.current_module=Modül +config.general.working_map=Harita +config.general.offensive=Saldırı Konfigürasyonu +config.general.offensive.desc=Yaratıkları öldürmek için kullanılır. +config.general.roam=Keşif Konfigürasyonu +config.general.roam.desc=Haritada gezinmek ve yaratıkları bulmak için kullanılır. +config.general.run=Kaçış Konfigürasyonu +config.general.run.desc=Haritalar arası geçiş ve kaçış için kullanılır. +config.general.formation_check=Uçuş Düzeni Kontrolü +config.general.formation_check.desc=Her X saniyede bir uçuş düzenini kontrol eder. Devre dışı bırakmak için 0 giriniz. +config.general.safety=Güvenlik +config.general.safety.repair_hp_range=Can Aralığında Tut (Savaş) +config.general.safety.repair_hp_range.desc=Yaratığa saldırı esnasındayken bile verilen can değeri altında botun kaçmasını/tamir etmesini sağlar. +config.general.safety.repair_hp_no_npc=Can Aralığında Tut (Keşif) +config.general.safety.repair_hp_no_npc.desc=Verilen can değeri altında botun tamir etmesini sağlar. +config.general.safety.repair_to_shield=Kalkan Tamir Yüzdesi +config.general.safety.repair_to_shield.desc=Verilen değere ulaşılana dek tamir etmeye devam eder. +config.general.safety.repair=Tamir Konfigürasyonu +config.general.safety.repair.desc=Tamir esnasında aktif olunması istenilen konfigürasyon. +config.general.safety.max_deaths=Maksimum ölüm +config.general.safety.max_deaths.desc=Ölüm sayısı girilen değere ulaştığında botu durdurur. +config.general.safety.revive_location=Yeniden Canlanma Lokasyonu +config.general.safety.revive_location.desc=Geminin patlaması ardından yeniden doğacağı lokasyon. +config.general.safety.revive_location.list.base=Merkez Üs +config.general.safety.revive_location.list.portal=Portal +config.general.safety.revive_location.list.spot=Patlanılan Nokta +config.general.safety.wait_before_revive=Yeniden Doğmadan Önce Bekle (Saniye) +config.general.safety.wait_before_revive.desc=Geminin patlaması ardından yeniden doğma öncesinde beklemesini sağlar. +config.general.safety.wait_after_revive=Yeniden Doğmadan Sonra Bekle (Saniye) +config.general.safety.wait_after_revive.des=Yeniden doğma ardından geminin beklemesini sağlar. +config.general.running=Kaçış +config.general.running.run_from_enemies=Düşmanlardan Kaç +config.general.running.run_from_enemies.desc=Size saldıran ve saldırmış olan düşmanlardan kaçmanızı sağlar. +config.general.running.remember_enemies_for=Düşmanları Hatırla +config.general.running.remember_enemies_for.desc=Girilen değer boyunca düşmanları hatırlar ve menzil içinde uzak durmanızı sağlar. +config.general.running.run_from_enemies_sight=Görüş Alanındaki Düşmanlardan Kaç +config.general.running.run_from_enemies_sight.desc=Görüş alanınız içine giren düşmanlardan kaçmanızı sağlar. +config.general.running.stop_running_no_sight=Görüş Alanından Çıkınca Kaçmayı Bırak +config.general.running.stop_running_no_sight.desc=Eğer düşman görüş alanınızın dışında kaldıysa kaçmayı bırakmanızı sağlar. +config.general.running.max_sight_distance=Maksimum Görüş Alanı +config.general.running.max_sight_distance.desc=Girilen alan dışında kalan düşmanları görmezden gelmenizi sağlar. +config.general.running.ship_ability=Gemi Kaçış Yeteneği +config.general.running.ship_ability.desc=Size saldırmış olan düşmanlardan kaçarken kullanılacak olan yetenek/tuş. +config.general.running.ship_ability_min=Kaçış Yeteneği Minimum Menzil +config.general.running.ship_ability_min.desc=Kaçış yeteğinin kullanılması için istenilen minimum menzil. +config.general.running.run_furthest_port=Maksimum En Yakın Portal Menzili +config.general.running.run_furthest_port.desc=Eğer maksimum menzilin dışındaysa düşmana en uzak olan portala kaçar. +config.general.roaming=Keşif ve Öncelikli Alan Ayarları +config.general.roaming.keep=Hedef noktaya ulaşana dek rotayı değiştirme +config.general.roaming.sequential=Öncelikli Alanları Sırayla Gez +config.general.roaming.only_kill_preferred=Yaratıkları Yalnızca Öncelikli Alanlarda Öldür +config.general.roaming.only_kill_preferred.desc=Yaratıklara sadece öncelikli alanlar içinde hedef alır ve öldürür. + +config.collect=Toplama +config.collect.stay_away_from_enemies=Düşmanlardan Uzak Dur +config.collect.auto_cloack=Otomatik Kamuflaj +config.collect.auto_cloack_key=Otomatik Kamuflaj Tuşu +config.collect.radius=Yaratık Öldürürken Toplama Menzili +config.collect.radius.desc=Yaratıkları öldürürken toplama için istenilen menzil. + +config.collect.box_table.resource=Kaynak +config.collect.box_table.resource.desc=Kutu ve diğer toplanabilen kaynaklar +config.collect.box_table.collect=Topla +config.collect.box_table.collect.desc=Eğer kaynağın toplanmasını istiyorsanız tıklayın. +config.collect.box_table.wait=Bekle(ms) +config.collect.box_table.wait.desc=Kaynağı toplarken üstünde beklenmesi gereken süreç +config.collect.box_table.priority=Öncelik +config.collect.box_table.priority.desc=Düşük sayı daha önceliklidir. + +config.loot=Yaratık Öldürme +config.loot.sab=SAB +config.loot.sab.desc=Otomatik olarak SAB cephanesi kullanır. +config.loot.sab.enabled=Aktif +config.loot.sab.key=SAB Cephane Tuşu +config.loot.sab.percent=Gerekli Kalkan Yüzdesi +config.loot.sab.npc_amount=Minimum Yaratık Kalkanı +config.loot.rsb=Rsb +config.loot.rsb.desc=Yaratıklar üzerinde RSB cephanesi kullanır. +config.loot.rsb.enabled=Aktif +config.loot.rsb.key=RSB Cephane Tuşu +config.loot.rsb.ammo_refresh=RSB Bekleme Süresi +config.loot.ammo_key=Cephane Tuşu +config.loot.ship_ability=Saldırı Yeteneği Tuşu +config.loot.ship_ability_min=Saldırı Yeteneği Minimum Can +config.loot.ship_ability_min.desc=Saldırı Yeteğinin kullanıabilmesi için yaratığın sahip olması gereken minimum can. +config.loot.max_circle_iterations=Akıllı Çember Tekrarı +config.loot.max_circle_iterations.desc=Çember yönünü değiştirmek için yapılan maksimum hareket. Devre dışı bırakmak için 0 giriniz. Tavsiye Edilen: 3-5 +config.loot.run_config_in_circle=Kovalamak İçin Kaçış Konfigürasyonu +config.loot.run_config_in_circle.desc=Kaçan yaratıkları kovalamak için kaçış konfigürasyonunu kullanır. +config.loot.group_npcs=Benzer Yaratıkları Gruplandır +config.loot.group_npcs.desc=Galaksi Kapısı yaratıklarını gruplandırır. +config.loot.npc_distance_ignore=Yaratıkları Yok Sayma Menzili + +config.loot.npc_table.name=İsim +config.loot.npc_table.name.desc=Yaratık isimleri, sonunda * olanlar gruplandırılmış olanlardır. +config.loot.npc_table.radius=Menzil +config.loot.npc_table.radius.desc=Yaratıktan uzak durulması gereken menzil +config.loot.npc_table.priority=Öncelik +config.loot.npc_table.priority.desc=Düşük sayı daha önceliklidir. +config.loot.npc_table.kill=Öldür +config.loot.npc_table.kill.desc=Eğer yaratığın öldürülmesini istiyorsanız tıklayın. +config.loot.npc_table.attack_key=Cephane Tuşu +config.loot.npc_table.attack_key.desc=Eğer bu yaratık için özel bir cephane kullanmak istiyorsanız, cephanenin olduğu tuşu girin. +config.loot.npc_table.attack_formation=Formasyon +config.loot.npc_table.attack_formation.desc=Seçili yaratık için tanımlanmış diroit formasyonu.Tanımlanmazsa varsayılan kullanılır. +config.loot.npc_table.extra=Ekstra +config.loot.npc_table.extra.desc=Yaratık için ekstra ayarlar + +config.loot.npc_table.extra.no_circle=Daire Çizme +config.loot.npc_table.extra.no_circle.short=DÇ +config.loot.npc_table.extra.no_circle.desc=Yaratık etrafında daire çizmez, sadece menzili korur. +config.loot.npc_table.extra.ignore_ownership=Sahipliği Yoksay +config.loot.npc_table.extra.ignore_ownership.short=SY +config.loot.npc_table.extra.ignore_ownership.desc=Sahip olmadığınız durumda da yaratığa saldırmanızı sağlar. +config.loot.npc_table.extra.ignore_attacked=Saldırılmış Olmasını Yoksay +config.loot.npc_table.extra.ignore_attacked.short=SOY +config.loot.npc_table.extra.ignore_attacked.desc=Daha önce saldırılmış yaratıklara da saldırmanızı sağlar. +config.loot.npc_table.extra.passive=Pasif +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Yaratık size saldırmadığı sürece yaratığı yoksaymanızı sağlar. +config.loot.npc_table.extra.attack_second=İkincil Saldır +config.loot.npc_table.extra.attack_second.short=İS +config.loot.npc_table.extra.attack_second.desc=Yalnızca zaten saldırılan yaratıklara saldırmanızı sağlar.
    SY ve SOY seçili olmak zorundadır. +config.loot.npc_table.extra.use_rsb=RSB Kullan +config.loot.npc_table.extra.use_rsb.short=RK +config.loot.npc_table.extra.use_rsb.desc=Bu yaratığa saldırırken RSB cephanesi kullan. +config.loot.npc_table.extra.pet_locator=NPC Bul +config.loot.npc_table.extra.pet_locator.short=DNPC +config.loot.npc_table.extra.pet_locator.desc=PET Düşman Bulma Modülünü Kullanır + +config.pet=PET +config.pet.enabled=PET'i Kullan +config.pet.enabled.desc=Eğer devre dışı bırakılırsa bot, tüm PET fonksiyonlarını devre dışı bırakacaktır +config.pet.module=PET Modülü +config.pet.module.desc=0 - Pasif
    1 - Koruma
    2 - Seçili Modüller vs. + +config.group=Grup +config.group.accept_invites=Davetleri Kabul Et +config.group.accept_invites.desc=Botun grup davetlerini kabul etmesini sağlar. +config.group.whitelist_invites=Sadece Beyaz Liste Davetleri +config.group.whitelist_invites.desc=Sadece beyaz listeye alınmış kullanıcılardan gelen davetleri kabul et. +config.group.invite_tag=Davet Yollayın +config.group.invite_tag.desc=Bu Etikete Sahip Oyuncuya Davet Yollar +config.group.open_invites=Davetleri Kabul Etme(Lider) +config.group.open_invites.desc=Grup davetiyesini herkes için Etkinleştir +config.group.kick_tag=Oyuncuyu At (Lider) +config.group.kick_tag.desc=Gruptan Oyuncu At + +config.miscellaneous=Diğer +config.miscellaneous.refresh_time=Her X Dakikada Yenile +config.miscellaneous.refresh_time.desc=Girilen dakikada bir botu yeniler. +config.miscellaneous.pause_for=Yenileme Ardından X Dakika Durdur +config.miscellaneous.pause_for.desc=Her yenileme ardından girilen dakika kadar botu beklemeye alır. +config.miscellaneous.drone_repair_percentage=Diroit Tamir Yüzdesi +config.miscellaneous.drone_repair_percentage.desc=Diroitler girilen yüzdenin üstüne çıktığında tamir eder. + +config.bot_settings=Bot Ayarları +config.bot_settings.lang=Dil +config.bot_settings.zone_resolution=Alan Hassasiyeti +config.bot_settings.zone_resolution.desc=Harita alan seçimlerinin hassasiyetini arttırır. +config.bot_settings.map_start_stop=Haritaya Tıklayarak Durdur/Başlat +config.bot_settings.map_start_stop.desc=Sol fare butonuyla botu durdurur/başlatır. +config.bot_settings.confirm_exit=Çıkışı Onayla +config.bot_settings.confirm_exit.desc=Botu kapatırken onay penceresini çıkmasını sağlar. +config.bot_settings.min_tick=Minimum Tıklama +config.bot_settings.dev_stuff=Geliştirici Özellikleri +config.bot_settings.dev_stuff.desc=Bu ayarı etkinleştirmek botun daha fazla CPU kullanmasına sebep olur +config.bot_settings.display=Görüntü +config.bot_settings.display.show_names=İsimleri Göster +config.bot_settings.display.show_names.desc=Küçük haritada oyuncu isimlerini gösterir. +config.bot_settings.display.hide_name=Oyuncu İsmini Gizle +config.bot_settings.display.hide_name.desc=Haritada yer alan oyuncu ismini gizler. +config.bot_settings.display.trail_length=İz Uzunluğu +config.bot_settings.display.trail_length.desc=Haritada bıraktığınız hareket izini belirler. +config.bot_settings.display.show_zones=Haritada Alan Bilgisini Göster +config.bot_settings.display.show_zones.desc=Haritada öncelikli ve yasaklı alanları gösterir. +config.bot_settings.display.button_size=GKA Buton Boyutu +config.bot_settings.display.button_size.desc=Ana penceredeki buton boyutunu düzenler. +config.bot_settings.display.hide_editors=Ayar Ekranını Tıklayana Dek Gizli Tut +config.bot_settings.display.hide_editors.desc=Yazı tabanlı ayar ekranına erişim sağlar. + +# Safety places +safety_places.type.portal=Portal +safety_places.type.cbs=KSİ +safety_places.type.base=Merkez Üs + +safety_places.run_mode= [Hepsi] Kullanım Koşulu +safety_places.run_mode.always=Her Zaman +safety_places.run_mode.enemy_flee_only=Kaçış (Düşman Menzilde) +safety_places.run_mode.repair_only=Tamir (Düşman Yok) +safety_places.run_mode.refresh=Yenileme +safety_places.run_mode.never=Asla + +safety_places.jump_mode= [Portal] Atlama Koşulu +safety_places.jump_mode.never=Asla +safety_places.jump_mode.escaping=Kaçış (Düşman Saldırısı) +safety_places.jump_mode.fleeing=Kaçış (Düşman Menzilde) +safety_places.jump_mode.repairing=Tamir +safety_places.jump_mode.always=Her Zaman +safety_places.jump_mode.always_other_side=Her Zaman (Diğer Tarafta Tamir Et) + +safety_places.cbs_mode= [KSİ] Kullanım Koşulu +safety_places.cbs_mode.ally=Dost KSİ +safety_places.cbs_mode.ally_neutral=Dost veya boş + +#Players +players.add_player=Oyuncu Ekle +players.add_player.by_name=Oyuncu İsmi +players.add_player.by_id=Oyuncu ID +players.add_player.username=Oyuncu: +players.add_player.user_id=Oyuncu ID: +players.add_player.invalid_id=Geçersiz oyuncu kimliği +players.add_player.invalid_id.no_number={0} Geçersiz oyuncu ID +players.remove_player.title=Eminmisiniz? +players.remove_player.msg=Seçilen {0} oynatıcısını gerçekten silmek istiyor musunuz? + +players.tag_default.unset=Ayarlanmadı +players.tag_default.all=Hepsi +players.tag_default.none=Hiçbiri +players.select_players.warn.title=Önce Oyuncuları Seçin +players.select_players.warn.add=Eklenmesini İstediğiniz Oyuncular +players.select_players.warn.remove=Silinmesini İstediğiniz Oyuncular +players.select_players.warn.remove_player=Kaldırmak istediğiniz Oyuncu +players.delete_tag.confirm.title=Eminmisiniz? +players.delete_tag.confirm.msg={0} etiketlerini silmek ve heryerten kaldırmak istiyor musunuz? + +# Plugins +plugins.reload=Eklentileri Yenile +plugins.config_button.enabled.desc=Ayarları Göster +plugins.config_button.disabled.desc=Ayarlar devre dışı, özellik yüklenmedi. +plugins.config_button.popup=Ayarları düzenleyemezsin. +plugins.config_button.popup.desc=Yüklenmemiş özellik için ayarlama yapamazsınız. \nÖzelliği aktif edip tekrar deneyin. + +plugins.issues.invalid_json=Geçersiz eklenti.json +plugins.issues.invalid_json.desc=Minimum versiyon ({0}) desteklenen versiyondan ({1}) yüksek. +plugins.issues.bot_update=Bot yükseltmesi gerekiyor. +plugins.issues.bot_update.desc=Bu eklenti ({0}) gerektiriyor ve Darkbot v{1} üzerinden çalıştırılamaz. +plugins.issues.loaded_twice=Eklenti Mevcut +plugins.issues.loaded_twice.desc=Aynı ada sahip bir eklenti zaten yüklendi +plugins.issues.plugin_update=Eklenti yükseltmesi gerekiyor. +plugins.issues.plugin_update.desc=Bu eklenti ({0}) gerektiriyor ve Darkbot v{1} üzerinden çalıştırılamaz. + +plugins.issues.signature.plugin_not_signed=İmzasız Eklenti +plugins.issues.signature.plugin_not_signed.desc=Bu eklenti imzalanmamış veya geçersiz bir imzaya sahip. +plugins.issues.signature.unknown_signature=Bilinmeyen imza +plugins.issues.signature.unknown_signature.desc=Bu eklenti bilinmeyen bir imzaya sahip. +plugins.issues.signature.invalid_signature=Invalid signature +plugins.issues.signature.invalid_signature.desc=Bu eklenti imzası geçersiz. + +# Main GUI +gui.hamburger_button.home=ANA SAYFA +gui.hamburger_button.reload=YENİLE +gui.hamburger_button.copy_sid=SID'i KOPYALA +gui.hamburger_button.discord=DISCORD +gui.config_button=Ayarları aç/kapa +gui.start_button= Botu durdur/başlat +gui.visibility_button=Tarayıcıyı göster/gizle +gui.pin_button=Her zaman üstte tut + +gui.exit=Çıkmak istediğinizden emin misiniz? +gui.exit.desc=Bu ayar bot ayarlarından devre dışı bırakılabilir. +gui.exit.yes_button=Evet +gui.exit.cancel_button=İptal + +gui.map.info=v{0} - Yenileme: {1}/{2} +gui.map.loading=Yükleniyor +gui.map.waiting=BEKLEME {0} +gui.map.running=ÇALIŞIYOR {0} + +# Modules +module.instructions.title=Talimatlar +module.kill_n_collect.status=Öldürme: {0} - Toplama: {1} + +module.map_travel.status.has_next={0} haritasına gidiliyor, sıradaki harita: {1} +module.map_travel.status.no_next={0} haritasına gidiliyor + +module.disconnect.status=Çıkış Yapılıyor ({0}): {1} +module.disconnect.status_stopped=Çıkış Yapıldı ({0}) +module.disconnect.status_paused=Durduruldu ({0}), {1} saniye içinde yeniden bağlanılıyor. +module.disconnect.reason.break=Mola Veriliyor +module.disconnect.reason.honor=Şeref Kaybı +module.disconnect.reason.draw_fire=Düşman Ateş Ediyor + +# Players +gui.players.not_found.title=Kullanıcı Bulunamadı +gui.players.not_found.by_name={0} adında bir kullanıcı bulunamadı. \nKullanıcıyı kullanıcı kimliğine göre eklemeyi deneyin +gui.players.not_found.by_id={0} kimliğine sahip kullanıcı bulunamadı. \nLütfen sunucunuza ait olduğundan emin olun + +# Issues +bot.issue.module_load_failed={0} eklentisi yüklenemedi, varsayılan kullanılıyor +bot.issue.config_load_failed=Ayar dosyası yüklenemedi.\nBot varsayılan ayarları kullanacak ve kaydetmeyecek.\nconfig.json dosyanızı silin(ayarlarınız kaybolacak) ve yeniden başlatın. +bot.issue.feature.failed_to_tick=İşaretlenemedi +bot.issue.feature.failed_to_load=Özellik yüklenemedi +bot.issue.feature.failed_to_load.desc={0} başarıyla kaydedilemedi: {1} + +bot.issue.resolution=Çözünürlüğü
    olarak değiştirmek risklidir!
    Sorunlarınız varsa
    varsayılana sıfırlayın! + +# Verifier +verifier.discord_warn.title=Resmi Discord kanalına katıl! +verifier.discord_warn.content.1=Bot bedavadır, eğer ödeme yaptıysanız veya reklam izlediyseniz dolandırıldınız demektir! +verifier.discord_warn.content.2=En güncel versiyona erişmek için Discord kanalımızda olduğunuzdan emin olun! +verifier.discord_warn.checkbox.dont_show=Bu mesajı bir daha gösterme +verifier.discord_warn.button.join_discord=Discord kanalına katıl! +verifier.discord_warn.button.ignore=Yoksay + +verifier.authenticate.error=Bot başlatılamadı. Bilgisayarınızı yeniden başlatmayı deneyin. +verifier.authenticate.title=Discord Doğrulaması +verifier.authenticate.content=Lütfen Discord kanalımızı yetkilendirin ve bu pencereyi kapatmayın.\nDoğrulama lokaldir ve her hangi bir sunucuya bir veri göndermeyecektir.\nEğer sayfa yüklenmediyse Chrome kullanmayı ve anti-virüs programınızı devre dışı bırakmayı deneyin. + +verifier.panel.invalid_auth=Doğrulama geçersiz. Botunuzu yeniden başlatmayı deneyin. +verifier.panel.authenticated.no_server={0} olarak doğrulandınız.
    Bir sunucuda görünmüyorsunuz. +verifier.panel.authenticated.servers={0} ve
      {1}
    üyesi olarak doğrulandınız. +verifier.panel.authenticated.join_official=Güncellemeleri almak için resmi Discord sunucumuza katılın!
    +verifier.panel.authenticated.blacklisted=Şuanda bu projeyi yapımcısının izni bulunmadan dağıtan, karalistedeki bir sunucunun üyesisiniz.
    Botu kullanmadan önce karalistedeki sunuculardan ayrılmak zorundasınız. Ayrıldıktan sonra yenilemek için tıklayın.
    " + +# Language changed +language.changed.title=Bot dili değiştirildi. +language.changed.content=Bot şuanda {1} tarafından çevrilmiş {0} dilini kullanmakta.\nDeğişikliklerin tam olarak uygulanması için botu yeniden başlatmanız tavsiye edilir. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@Syphtie#5379 & @SAİTB.ÇALIŞKAN#9654 \ No newline at end of file diff --git a/src/main/resources/lang/strings_uk.properties b/src/main/resources/lang/strings_uk.properties new file mode 100644 index 00000000..eb5c0e55 --- /dev/null +++ b/src/main/resources/lang/strings_uk.properties @@ -0,0 +1,328 @@ +# Config tabs +tabs.general=Головна +tabs.general.desc=Загальні налаштування бота +tabs.preferred_zones=Переважні зони +tabs.preferred_zones.desc=Зони, у які бот буде випадково пересуватися +tabs.avoided_zones=Заборонені зони +tabs.avoided_zones.desc=Зони, які розглядаються як бар'єр +tabs.safety_places=Безпечні місця +tabs.safety_places.desc=Безпечні місця до яких можна втекти за для ремонту +tabs.players=Гравці +tabs.plugins.desc=Інформація та конфігурація встановлених плагінів + +# Config tree +config.general=Головна +config.general.current_module=Модуль +config.general.working_map=Робоча мапа +config.general.offensive=Захисна конфігурація +config.general.offensive.desc=Використовується для вбивства NPC +config.general.roam=Пошукова конфігурація +config.general.roam.desc=Використовується для пошуку NPC на мапі +config.general.run=Конфігурація втечі +config.general.run.desc=Використовується для втечі та перемикання карт +config.general.formation_check=Перевірити формацію +config.general.formation_check.desc=Перевіряє кожні Х секунд, яка формація ввімкнена +config.general.safety=Безпека +config.general.safety.repair_hp_range=Тримати здоров'я між +config.general.safety.repair_hp_range.desc=Навіть якщо вбиває NPC, все одно тікає якщо HP нижче +config.general.safety.repair_hp_no_npc=Ремонтувати коли йде пошук та коли HP нижче +config.general.safety.repair_hp_no_npc.desc=Поріг HP для втечі коли йде пошук +config.general.safety.repair_to_shield=Ремонтувати до поки щит нижче ніж +config.general.safety.repair_to_shield.desc=Мінімальне значення щиту для зупинки ремонту +config.general.safety.repair=Конфігурація ремонту +config.general.safety.repair.desc=Використовується для ремонту після втечі допоки щити будуть повні +config.general.safety.max_deaths=Кількість смертей +config.general.safety.max_deaths.desc=Макс. кількість смертей для запинки бота +config.general.safety.revive_location=Позиція відродження +config.general.safety.revive_location.desc=Де відродити корабель +config.general.safety.revive_location.list.base=База +config.general.safety.revive_location.list.portal=Порт +config.general.safety.revive_location.list.spot=На місці +config.general.safety.wait_before_revive=Чекати до відродження (сек) +config.general.safety.wait_before_revive.desc=Скільки секунд чекати перед відродженням +config.general.safety.wait_after_revive=Чекати після відродження (сек) +config.general.safety.wait_after_revive.des=Скільки секунд чекати після відродженням +config.general.running=Втеча +config.general.running.run_from_enemies=Втікати від ворогів +config.general.running.run_from_enemies.desc=Обрати, якщо бот повинен в втікати від тих хто атакує чи атакував раніше +config.general.running.remember_enemies_for=Запам'ятовувати ворогів напротязі +config.general.running.remember_enemies_for.desc=Наскільки довго пам'ятати ворогів що атакували +config.general.running.run_from_enemies_sight=Втікати від ворогів що в зоні досяжності +config.general.running.run_from_enemies_sight.desc=Втікати від ворогів що попали у вашу міні карту +config.general.running.stop_running_no_sight=Зупинити втечу коли вийшов з зони досяжності +config.general.running.stop_running_no_sight.desc=Зупинити втечу коли ворога не видно на міні карті +config.general.running.max_sight_distance=Макс. зона досяжності +config.general.running.max_sight_distance.desc=Не дозволяє бачити ворогів на відстані більш ніж це значення +config.general.running.ship_ability=Вміння корабля +config.general.running.ship_ability.desc=Використовується коли втікає від ворогів що атакують +config.general.running.ship_ability_min=Мін. дистанція для використання вміння +config.general.running.ship_ability_min.desc=Мін. дистанція до безпечної зони для використання вміння корабля +config.general.running.run_furthest_port=Дистанція до найближчого порту +config.general.running.run_furthest_port.desc=Втікати до порту який знаходиться ближче ніж це значення +config.general.roaming=Пошук та Функція "обраних зон" +config.general.roaming.keep=Продовжувати пошук допоки не буде досягнута точка обраної зони +config.general.roaming.sequential=Пошук тільки у обраних зонах +config.general.roaming.only_kill_preferred=Вбивати NPC тільки у обраних зонах +config.general.roaming.only_kill_preferred.desc=Обирає лише тих NPC що знаходяться в обраній зоні + +config.collect=Збір +config.collect.stay_away_from_enemies=Триматися подалі від ворогів +config.collect.auto_cloack=Авто-невидимість +config.collect.auto_cloack_key=Кнопка авто-невидимості +config.collect.radius=Збирати, коли атакує ворогів +config.collect.radius.desc=Радіус збору ресурсів коли атакує ворогів + +config.collect.box_table.resource=Ресурс +config.collect.box_table.resource.desc=Коробки або інші ресурси, які бот бачив +config.collect.box_table.collect=Збір +config.collect.box_table.collect.desc=Якщо ресурс повинен бути зібраний +config.collect.box_table.wait=Очікування (ms) +config.collect.box_table.wait.desc=Час очікування, щоб забрати ресурс, у мілісекундах +config.collect.box_table.priority=Пріоритет +config.collect.box_table.priority.desc=Пріоритет № 1 буде отримано до пріоритету № 2 + +config.loot=NPC-кіллер +config.loot.sab=Sab +config.loot.sab.desc=Автоматично вмикати SAB (NPC живуть довше) +config.loot.sab.enabled=Ввімкнено +config.loot.sab.key=Кнопка +config.loot.sab.percent=Здоров'я понад +config.loot.sab.npc_amount=Мінімальне значення щиту у NPC +config.loot.rsb=RSB +config.loot.rsb.desc=Використовування RSB боєприпасу проти NPC у котрих є UR параметр +config.loot.rsb.enabled=Ввімкнено +config.loot.rsb.key=RSB кнопка +config.loot.rsb.ammo_refresh=RSB перезавантаження +config.loot.ammo_key=Кнопка боєприпасу +config.loot.ship_ability=Кнопка захисного вміння корабля +config.loot.ship_ability_min=Мін. HP для використання захисного вміння +config.loot.ship_ability_min.desc=Мін. здоров'я NPC для використання захисного вміння +config.loot.max_circle_iterations=Кількість повторів розумного кружляння +config.loot.max_circle_iterations.desc=Кількість рухів кружляння перед тим як змінити напрямок руху, 0 - вимкнено, рекомендовано 3-5 +config.loot.run_config_in_circle=Конфігурація втечі для погоні +config.loot.run_config_in_circle.desc=Використовувати конф. втечі для того щоб наздогнати втікаючого NPC +config.loot.group_npcs=Групувати однакових NPC +config.loot.group_npcs.desc=Групувати однакових NPC у GG в один порт +config.loot.npc_distance_ignore=Ігнорувати NPC які далі ніж + +config.loot.npc_table.name=Імена +config.loot.npc_table.name.desc=Імена NPC, що закінчуються на * NPC згруповані +config.loot.npc_table.radius=Радіус +config.loot.npc_table.radius.desc=Відстань від цього NPC +config.loot.npc_table.priority=Пріоритет +config.loot.npc_table.priority.desc=Пріоритет № 1 буде націлений на пріоритет № 2 +config.loot.npc_table.kill=Вбивство +config.loot.npc_table.kill.desc=Якщо цей NPC повинен бути вбитий +config.loot.npc_table.attack_key=Боєприпаси +config.loot.npc_table.attack_key.desc=Спеціальні боєприпаси для цього NPC, якщо не встановлено, використовується за замовченням +config.loot.npc_table.attack_formation=Формація +config.loot.npc_table.attack_formation.desc=Спеціальна формація для цього типу NPC, якщо не налаштовано буде використана бойова формація +config.loot.npc_table.extra=Додатковий +config.loot.npc_table.extra.desc=Декілька додаткових флагів для NPC + +config.loot.npc_table.extra.no_circle=Немає круга +config.loot.npc_table.extra.no_circle.short=NC +config.loot.npc_table.extra.no_circle.desc=Не обводить NPC, просто залишається у радіусі +config.loot.npc_table.extra.ignore_ownership=Ігнорувати право власності +config.loot.npc_table.extra.ignore_ownership.short=IO +config.loot.npc_table.extra.ignore_ownership.desc=Продовжує вбивати NPC, навіть якщо його б'є інший +config.loot.npc_table.extra.ignore_attacked=Ігнорувати якщо інші атакують +config.loot.npc_table.extra.ignore_attacked.short=IA +config.loot.npc_table.extra.ignore_attacked.desc=Обирайте NPC, навіть якщо інші гравці його б'ють +config.loot.npc_table.extra.passive=Пасивний +config.loot.npc_table.extra.passive.short=P +config.loot.npc_table.extra.passive.desc=Залишається пасивним проти цього NPC, стріляє у NPC якщо той стріляє у вас +config.loot.npc_table.extra.attack_second=Друга атака +config.loot.npc_table.extra.attack_second.short=AS +config.loot.npc_table.extra.attack_second.desc=Стріляє якщо інші гравці теж атакують.
    Повинен бути також флаг на ігнорування атаки іншими гравцями +config.loot.npc_table.extra.use_rsb=Використовувати RSB +config.loot.npc_table.extra.use_rsb.short=UR +config.loot.npc_table.extra.use_rsb.desc=Використовувати RSB боєприпаси для атаки NPC +config.loot.npc_table.extra.pet_locator=Локатор PET +config.loot.npc_table.extra.pet_locator.short=PL +config.loot.npc_table.extra.pet_locator.desc=Використовувати PET локатор для знаходження NPC + +config.pet=PET +config.pet.enabled=Використовувати PET +config.pet.enabled.desc=Якщо вимкнено, бот не буде використовувати PET +config.pet.module=# використовуваного модуля PET +config.pet.module.desc=0 - Пасивний
    1 - Захисний режим
    2,3... - Усі наступні по порядку + +config.group=Угрупування +config.group.accept_invites=Приймати запрошення +config.group.accept_invites.desc=MДозволити боту приймати запрошення в угрупування +config.group.whitelist_tag=Приймати запрошення від +config.group.whitelist_tag.desc=Приймати запрошення тільки від обраних гравців +config.group.invite_tag=Відправляти запрошення +config.group.invite_tag.desc=Відправляти запрошення з цим тегом +config.group.open_invites=Зробити угрупування відкритим +config.group.open_invites.desc=Дозволити кому-завгодно відправляти запрошення в угрупування +config.group.kick_tag=Вигнати гравця (тільки лідер угрупування) +config.group.kick_tag.desc=Вигнати гравця з угрупування + +config.miscellaneous=Розвинуте +config.miscellaneous.refresh_time=Перезапускати кожні +config.miscellaneous.refresh_time.desc=Кожні Х хвилин перезапускати гру +config.miscellaneous.pause_for=Пауза після перезапуску +config.miscellaneous.pause_for.desc=Скільки хвилин паузи робити після перезапуску +config.miscellaneous.drone_repair_percentage=Процент пошкодження дронів +config.miscellaneous.drone_repair_percentage.desc=Ремонтувати дронів коли процент пошкодження більш ніж + +config.bot_settings=Налаштування бота +config.bot_settings.locale=Мова +config.bot_settings.zone_resolution=Розмір зон +config.bot_settings.zone_resolution.desc=Кількість ділень на мапі налаштування обраних зон бота +config.bot_settings.map_start_stop=Старт/Пауза по кліку на мапу +config.bot_settings.map_start_stop.desc=Лівий клік для старту/паузи. Правий - переміщення корабля. +config.bot_settings.confirm_exit=Підтвердження виходу +config.bot_settings.confirm_exit.desc=Показувати підтвердження перед виходом +config.bot_settings.min_tick=Час одного тіку(одиниця оновлення) +config.bot_settings.dev_stuff=Відобразити інструменти розробника +config.bot_settings.dev_stuff.desc=Ввімкнення цього ПОТРЕБУЄ більше ресурсів процесора +config.bot_settings.display=Відображення +config.bot_settings.display.show_names=Показувати нікнейми гравців +config.bot_settings.display.show_names.desc=Показувати нікнейми гравців на міні карті +config.bot_settings.display.hide_name=Сховати ім'я +config.bot_settings.display.hide_name.desc=Сховати ваше ім'я на мапі +config.bot_settings.display.hide_group_names=Сховати імена угрупування +config.bot_settings.display.hide_group_names.desc=Сховати імена гравців в поточному угрупуванні +config.bot_settings.display.trail_length=Довжина хвоста +config.bot_settings.display.trail_length.desc=Кількість часу, який хвіст залишається на мапі (хвіст - біла лінія) +config.bot_settings.display.show_zones=Відображати обрані зони на мапі +config.bot_settings.display.show_zones.desc=Відображає обрані зони на мапі +config.bot_settings.display.button_size=Розмір кнопок інтерфейсу +config.bot_settings.display.button_size.desc=Змінює вкладинки у конфігурації та змінює розмір вікна + +# Safety places +safety_places.type.portal=Порт +safety_places.type.cbs=Кланова база +safety_places.type.base=База + +safety_places.run_mode= [Все] Використовуйте умову +safety_places.run_mode.always=Завжди +safety_places.run_mode.enemy_flee_only=Втікати коли (Ворог у зорі) +safety_places.run_mode.repair_only=Ремонт (Нема ворогів) +safety_places.run_mode.refresh=Оновлення +safety_places.run_mode.never=Ніколи + +safety_places.jump_mode= [Портал] Умова стрибка +safety_places.jump_mode.never=Ніколи +safety_places.jump_mode.escaping=Втікати коли (Ворог стріляє по вам) +safety_places.jump_mode.fleeing=Втікати коли (Ворог у зорі) +safety_places.jump_mode.repairing=Ремонт +safety_places.jump_mode.always=Завжди +safety_places.jump_mode.always_other_side=Завжди (Ремонт на іншій стороні) + +safety_places.cbs_mode= [Кланова база] Використовувати умову +safety_places.cbs_mode.ally=Союзна база +safety_places.cbs_mode.ally_neutral=Союзна база або пустий камінь + +#Players +players.add_player=Додати гравця +players.add_player.by_name=Гравець за ім'ям +players.add_player.by_id=Гравець за ID +players.add_player.username=Username: +players.add_player.user_id=ID гравця: +players.add_player.invalid_id=Некорректний ID гравця +players.add_player.invalid_id.no_number={0} некорректне число + +players.tag_default.unset=Невизначено +players.tag_default.all=Усі +players.tag_default.none=Ніхто +players.select_players.warn.title=Вибрати спершу гравця +players.select_players.warn.add=Обрати гравця якого ви теж бажаєте додати до тегу +players.select_players.warn.remove=Обрати гравця якого ви бажаєте видалити з тегу +players.delete_tag.confirm.title=Ви впевнені? +players.delete_tag.confirm.msg=Ви впевнені що бажаєте видалити тег {0}, та видалити звідти усіх? + +# Plugins +plugins.reload=Перезавантажити плагіни +plugins.config_button.enabled.desc=Показати конфігурацію +plugins.config_button.disabled.desc=Конфіг вимкнений, функція не активна +plugins.config_button.popup=Не можу відреагувати конфігурацію +plugins.config_button.popup.desc=Конфігурація недоступна для вигруженої функції \nВвімкніть функцію і завантажте її, потім повторіть спробу. + +plugins.issues.invalid_json=Невірний плагін .json +plugins.issues.invalid_json.desc=Мінімальна версія {0} вище, ніж підтримувана версія {1} +plugins.issues.bot_update=Потребує оновлення бота +plugins.issues.bot_update.desc=Цей плагін потребує {0}, тому він не може працювати у DarkBot v{1} +plugins.issues.plugin_update=Плагіну може потребуватися оновлення +plugins.issues.plugin_update.desc=Плагін створений для {0}, тому він може не працювати на DarkBot v{1} + +plugins.issues.signature.plugin_not_signed=Плагін не підписан +plugins.issues.signature.plugin_not_signed.desc=Цей плагін не був підписаний або має не вірний підпис +plugins.issues.signature.unknown_signature=Незнайомий підпис +plugins.issues.signature.unknown_signature.desc=Цей плагін був підписан з ненадійним сертифікатом +plugins.issues.signature.invalid_signature=Невірний підпис +plugins.issues.signature.invalid_signature.desc=Плагін має невірний підпис або був підроблений + +# Main GUI +gui.hamburger_button.home=Відкрити головну сторінку в браузері +gui.hamburger_button.reload=Оновити осн. стор. бота +gui.hamburger_button.copy_sid=Копіювати SID +gui.hamburger_button.discord=Дискорд +gui.config_button=Показати / Сховати конфігурацію +gui.start_button=Старт / Стоп +gui.visibility_button=Показати / Сховати браузер +gui.pin_button=Поверх усіх вікон(вімк/вимк) + +gui.exit=Ви впевнені, що бажаєте вийти? +gui.exit.desc=Підтвердити вихід з боту(можна вимкнути в налаштуваннях боту) +gui.exit.yes_button=Так +gui.exit.cancel_button=Відмінити + +gui.map.info=v{0} - Оновлення: {1}/{2} +gui.map.loading=Завантаження +gui.map.waiting=ОЧІКУВАННЯ {0} +gui.map.running=ПРАЦЮЮ {0} + +# Modules +module.instructions.title=Інструкції +module.kill_n_collect.status=Вбити: {0} - зібрати: {1} + +module.map_travel.status.has_next=Прямує до {0}, наступна мапа: {1} +module.map_travel.status.no_next=Прямує до {0} + +module.disconnect.status=Відключення ({0}): {1} +module.disconnect.status_stopped=Відключено ({0}) +module.disconnect.status_paused=Призупинено ({0}), перепідключення через {1} +module.disconnect.reason.break=Перерва +module.disconnect.reason.honor=Втрачена честь +module.disconnect.reason.draw_fire=Ворог використав шквальний вогонь + +# Players +gui.players.not_found.title=Гравця не знайдено +gui.players.not_found.by_name=Ми не змогли знайти гравця з ім'ям {0}.\nСпробуйте його знайти за ID. +gui.players.not_found.by_id=Гравця з ID {0} не знайдено.\nВпевніться, що такий гравець існує на цьому сервері чи в грі взагалі + +# Issues +bot.issue.module_load_failed=Не вдалося завантажити модуль {0}, використовуючи значення за замовченням +bot.issue.config_load_failed=Не вдалося завантажити файл конфігурації.\nБот буде використовувати конфігурацію за замовченням та не збереже її.\nВидаліть файл config.json (видалить налаштування) та перезавантажте бота. +bot.issue.feature.failed_to_tick=Не вдалося зробити тік часу +bot.issue.feature.failed_to_load=Не вдалося завантажити функцію +bot.issue.feature.failed_to_load.desc={0} не може бути вірно зареєстрований: {1} + +# Verifier +verifier.discord_warn.title=Приєднуйтесь до офіційного дискорду! +verifier.discord_warn.content.1=Цей бот є безкоштовним. Якщо ви за нього платили або бачите рекламу - вас обманули! +verifier.discord_warn.content.2=Переконайтеся, що ви в офіційній спільноті Дискорд для отримання безкоштовних оновлень! +verifier.discord_warn.checkbox.dont_show=Не відображати це повідомлення знову +verifier.discord_warn.button.join_discord=Приєднатися до дискорду +verifier.discord_warn.button.ignore=Ігнорувати + +verifier.authenticate.error=Бот стартував не вірно. Спробуйте перезавантажити ПК. +verifier.authenticate.title=Аутентифікація дискорд +verifier.authenticate.content=Слідуйте інструкціям у вікні браузера. Не закривайте це вікно.\nАутентифікація є локальною та не відправляє дані на сервер.\nЯкщо сторінка не завантажується у вашому браузері, спробуйте Хром або вимкніть антивірус. + +verifier.panel.invalid_auth=Невірна аутентифікація. Спробуйте перезавантажити ПК. +verifier.panel.authenticated.no_server=Ви були аутентифіковані як {0}
    Схоже, вас немає на жодному сервері бота. +verifier.panel.authenticated.servers=Ви були аутентифіковані як {0}, учасник :
      {1}
    +verifier.panel.authenticated.join_official=Ви повинні приєднатися до офіційного серверу для отримання оновлень!
    +verifier.panel.authenticated.blacklisted=Ви учасник спільноти що вкрала, або використовує мою роботу без права на це.
    Ви повинні покинути заборонені сервера перед використанням бота. Натисніть "Update" після того як вийдете з заборонених спільнот.
    " + +# Language changed +language.changed.title=Язик бота змінився +language.changed.content=Тепер бот використовує переклад {0}, створений {1}.\nРекомендується перезавантажити бота для застування змін. + +# Translation credits. Add anyone who worked on the translations for this language here: +translation.credit=@4e4enko#8136 \ No newline at end of file diff --git a/src/main/resources/maximize.png b/src/main/resources/maximize.png new file mode 100644 index 00000000..1e6d261b Binary files /dev/null and b/src/main/resources/maximize.png differ diff --git a/src/main/resources/minimize.png b/src/main/resources/minimize.png new file mode 100644 index 00000000..1530fe64 Binary files /dev/null and b/src/main/resources/minimize.png differ diff --git a/src/main/resources/pause.png b/src/main/resources/pause.png new file mode 100644 index 00000000..d156b9cf Binary files /dev/null and b/src/main/resources/pause.png differ diff --git a/src/main/resources/pin.png b/src/main/resources/pin.png new file mode 100644 index 00000000..b083b277 Binary files /dev/null and b/src/main/resources/pin.png differ diff --git a/src/main/resources/play.png b/src/main/resources/play.png new file mode 100644 index 00000000..7ade1672 Binary files /dev/null and b/src/main/resources/play.png differ diff --git a/src/main/resources/plugins.png b/src/main/resources/plugins.png new file mode 100644 index 00000000..353dd8f1 Binary files /dev/null and b/src/main/resources/plugins.png differ diff --git a/src/main/resources/plugins_warn.png b/src/main/resources/plugins_warn.png new file mode 100644 index 00000000..1905492b Binary files /dev/null and b/src/main/resources/plugins_warn.png differ diff --git a/src/main/resources/reload.png b/src/main/resources/reload.png new file mode 100644 index 00000000..9508ac80 Binary files /dev/null and b/src/main/resources/reload.png differ diff --git a/src/main/resources/remove.png b/src/main/resources/remove.png new file mode 100644 index 00000000..d4f5011c Binary files /dev/null and b/src/main/resources/remove.png differ diff --git a/src/main/resources/search.png b/src/main/resources/search.png new file mode 100644 index 00000000..aedc139e Binary files /dev/null and b/src/main/resources/search.png differ diff --git a/src/main/resources/stats.png b/src/main/resources/stats.png new file mode 100644 index 00000000..a45c6605 Binary files /dev/null and b/src/main/resources/stats.png differ diff --git a/src/main/resources/tray.png b/src/main/resources/tray.png new file mode 100644 index 00000000..7129c04e Binary files /dev/null and b/src/main/resources/tray.png differ diff --git a/src/main/resources/unpin.png b/src/main/resources/unpin.png new file mode 100644 index 00000000..2e6cad7e Binary files /dev/null and b/src/main/resources/unpin.png differ diff --git a/src/main/resources/visibility-off.png b/src/main/resources/visibility-off.png new file mode 100644 index 00000000..270058ac Binary files /dev/null and b/src/main/resources/visibility-off.png differ diff --git a/src/main/resources/visibility.png b/src/main/resources/visibility.png new file mode 100644 index 00000000..734ed124 Binary files /dev/null and b/src/main/resources/visibility.png differ