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 a0e25d5c9..99ed11930 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/Config.java +++ b/src/main/java/com/github/manolo8/darkbot/config/Config.java @@ -12,7 +12,6 @@ import com.github.manolo8.darkbot.core.manager.StarManager; import com.github.manolo8.darkbot.core.utils.Lazy; import com.github.manolo8.darkbot.gui.MainGui; -import com.github.manolo8.darkbot.gui.tree.editors.CharacterEditor; import com.github.manolo8.darkbot.gui.tree.utils.NpcTableModel; import com.github.manolo8.darkbot.gui.tree.utils.TableHelpers; import com.github.manolo8.darkbot.utils.OSUtil; @@ -25,6 +24,7 @@ import eu.darkbot.api.config.annotations.Tag; import eu.darkbot.api.config.annotations.Visibility; import eu.darkbot.api.config.annotations.Visibility.Level; +import eu.darkbot.api.config.types.ShipMode; import eu.darkbot.api.game.enums.PetGear; import eu.darkbot.api.game.items.ItemCategory; import eu.darkbot.api.game.items.SelectableItem; @@ -41,6 +41,7 @@ import java.util.LinkedList; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.Set; @@ -76,44 +77,71 @@ public class Config implements eu.darkbot.api.config.legacy.Config { public transient long changedAt; public @Option General GENERAL = new General(); + public static class General { - @Option @Dropdown(options = ModuleSupplier.class) + @Option + @Dropdown(options = ModuleSupplier.class) public String CURRENT_MODULE = LootCollectorModule.class.getCanonicalName(); - public @Option @Dropdown(options = StarManager.MapOptions.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 + @Dropdown(options = StarManager.MapOptions.class) int WORKING_MAP = 26; + public @Option ShipConfig OFFENSIVE = new ShipConfig(HeroAPI.Configuration.FIRST, SelectableItem.Formation.STANDARD); + public @Option ShipConfig ROAM = new ShipConfig(HeroAPI.Configuration.SECOND, SelectableItem.Formation.STANDARD); + public @Option ShipConfig RUN = new ShipConfig(HeroAPI.Configuration.SECOND, SelectableItem.Formation.STANDARD); public @Option Safety SAFETY = new Safety(); + public static class Safety { public @Option PercentRange REPAIR_HP_RANGE = new PercentRange(0.4, 0.95); - public @Option @Percentage double REPAIR_HP_NO_NPC = 0.5; - public @Option @Percentage double REPAIR_TO_SHIELD = 1; - public @Option ShipConfig REPAIR = new ShipConfig(1, '9'); - public @Option @Number(min = 1, max = 9999) @Number.Disabled(value = -1, def = 10) int MAX_DEATHS = 10; + public @Option + @Percentage double REPAIR_HP_NO_NPC = 0.5; + public @Option + @Percentage double REPAIR_TO_SHIELD = 1; + public @Option ShipConfig REPAIR = new ShipConfig(HeroAPI.Configuration.FIRST, SelectableItem.Formation.STANDARD); + public @Option + @Number(min = 1, max = 9999) + @Number.Disabled(value = -1, def = 10) int MAX_DEATHS = 10; @Deprecated public long REVIVE_LOCATION = 1L; - public @Option("config.general.safety.revive_location") @Dropdown ReviveLocation REVIVE = ReviveLocation.BASE; - public @Option @Visibility(Level.INTERMEDIATE) @Number(min = 5, max = 60, step = 10) int WAIT_BEFORE_REVIVE = 5; - public @Option @Visibility(Level.INTERMEDIATE) @Number(min = 3, max = 15 * 60, step = 10) int WAIT_AFTER_REVIVE = 90; - public @Option @Visibility(Level.INTERMEDIATE) - @Number(min = 0, max = 99999, step = 50) @Number.Disabled(value = 0, def = 500) int INSTANT_REPAIR = 0; + public @Option("config.general.safety.revive_location") + @Dropdown ReviveLocation REVIVE = ReviveLocation.BASE; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(min = 5, max = 60, step = 10) int WAIT_BEFORE_REVIVE = 5; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(min = 3, max = 15 * 60, step = 10) int WAIT_AFTER_REVIVE = 90; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(min = 0, max = 99999, step = 50) + @Number.Disabled(value = 0, def = 500) int INSTANT_REPAIR = 0; } public @Option Running RUNNING = new Running(); + public static class Running { public @Option boolean RUN_FROM_ENEMIES = true; - public @Option @Tag(Tag.Default.NONE) PlayerTag ENEMIES_TAG = null; - public @Option @Visibility(Level.INTERMEDIATE) @Number(max = 24 * 60 * 60, step = 300) int REMEMBER_ENEMIES_FOR = 300; + public @Option + @Tag(Tag.Default.NONE) PlayerTag ENEMIES_TAG = null; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(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 @Visibility(Level.INTERMEDIATE) @Number(min = 500, max = 20000, step = 500) int MAX_SIGHT_DISTANCE = 4000; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(min = 500, max = 20000, step = 500) int MAX_SIGHT_DISTANCE = 4000; public @Option Character SHIP_ABILITY; - public @Option @Visibility(Level.INTERMEDIATE) @Number(max = 20000, step = 500) int SHIP_ABILITY_MIN = 1500; - public @Option @Visibility(Level.INTERMEDIATE) @Number(max = 20000, step = 500) int RUN_FURTHEST_PORT = 1500; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(max = 20000, step = 500) int SHIP_ABILITY_MIN = 1500; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(max = 20000, step = 500) int RUN_FURTHEST_PORT = 1500; } - public @Option @Visibility(Level.INTERMEDIATE) Roaming ROAMING = new Roaming(); + public @Option + @Visibility(Level.INTERMEDIATE) Roaming ROAMING = new Roaming(); + public static class Roaming { public @Option boolean KEEP = true; public @Option boolean SEQUENTIAL = false; @@ -123,11 +151,13 @@ public static class Roaming { } 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 @Number(max = 10000, step = 50) int RADIUS = 400; + public @Option + @Number(max = 10000, step = 50) int RADIUS = 400; public @Option boolean IGNORE_CONTESTED_BOXES = true; @Option @@ -137,25 +167,37 @@ public static class Collect { } 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 @Percentage double PERCENT = 0.8; - public @Option @Number(min = 500, max = 1_000_000, step = 1000) int NPC_AMOUNT = 12000; - public @Option @Visibility(Level.ADVANCED) Condition CONDITION; + public @Option + @Percentage double PERCENT = 0.8; + public @Option + @Number(min = 500, max = 1_000_000, step = 1000) int NPC_AMOUNT = 12000; + public @Option + @Visibility(Level.ADVANCED) Condition CONDITION; } + public static class Rsb { public @Option boolean ENABLED = false; public @Option Character KEY = '3'; public @Deprecated int AMMO_REFRESH = 3500; } + public @Option Character AMMO_KEY = '1'; - public @Option @Visibility(Level.INTERMEDIATE) Character SHIP_ABILITY; - public @Option @Visibility(Level.INTERMEDIATE) @Number(min = 50_000, max = 5_000_000, step = 50_000) int SHIP_ABILITY_MIN = 150_000; - public @Option @Visibility(Level.ADVANCED) @Number(max = 10, step = 1) int MAX_CIRCLE_ITERATIONS = 5; + public @Option + @Visibility(Level.INTERMEDIATE) Character SHIP_ABILITY; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(min = 50_000, max = 5_000_000, step = 50_000) int SHIP_ABILITY_MIN = 150_000; + public @Option + @Visibility(Level.ADVANCED) + @Number(max = 10, step = 1) int MAX_CIRCLE_ITERATIONS = 5; public @Option boolean RUN_CONFIG_IN_CIRCLE = true; public @Option boolean GROUP_NPCS = true; @@ -166,20 +208,27 @@ public static class Rsb { public @Option Map NPC_INFOS = new HashMap<>(); public transient Lazy MODIFIED_NPC = new Lazy.NoCache<>(); - public @Option @Visibility(Level.INTERMEDIATE) @Number(min = 1000, max = 20000, step = 500) int NPC_DISTANCE_IGNORE = 3000; + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(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 @Option @Dropdown(options = PetGears.class) PetGear MODULE_ID = PetGear.PASSIVE; + public @Option + @Dropdown(options = PetGears.class) PetGear MODULE_ID = PetGear.PASSIVE; } public @Option GroupSettings GROUP = new GroupSettings(); + public static class GroupSettings { public @Option boolean ACCEPT_INVITES = false; - public @Option @Tag(Tag.Default.ALL) PlayerTag WHITELIST_TAG = null; - public @Option @Tag(Tag.Default.NONE) PlayerTag INVITE_TAG = null; + public @Option + @Tag(Tag.Default.ALL) PlayerTag WHITELIST_TAG = null; + public @Option + @Tag(Tag.Default.NONE) PlayerTag INVITE_TAG = null; public @Option boolean OPEN_INVITES = false; public @Option boolean BLOCK_INVITES = false; public @Option boolean LEAVE_NO_WHITELISTED = false; @@ -188,34 +237,55 @@ public static class GroupSettings { } public @Option Miscellaneous MISCELLANEOUS = new Miscellaneous(); + public static class Miscellaneous { public @Option boolean REFRESH_AFTER_REVIVE = false; - public @Option @Number(max = 60 * 12, step = 10) int REFRESH_TIME = 60; - public @Option @Number(max = 60 * 12, step = 10) int PAUSE_FOR = 0; + public @Option + @Number(max = 60 * 12, step = 10) int REFRESH_TIME = 60; + public @Option + @Number(max = 60 * 12, step = 10) int PAUSE_FOR = 0; public @Option boolean RESET_REFRESH = true; public @Option boolean SOLVE_BACKPAGE_CAPTCHA = false; - public @Option @Visibility(Level.INTERMEDIATE) boolean UPDATE_STATS_WHILE_PAUSED = true; - public @Option @Visibility(Level.INTERMEDIATE) @Percentage double DRONE_REPAIR_PERCENTAGE = 0.9; - public @Option @Visibility(Level.INTERMEDIATE) boolean HONOR_LOST_EXACT = true; - public @Option @Visibility(Level.INTERMEDIATE) boolean LOG_CHAT = false; - public @Option @Visibility(Level.INTERMEDIATE) boolean LOG_DEATHS = false; - public @Option @Visibility(Level.INTERMEDIATE) boolean AVOID_MINES = true; - public @Option @Visibility(Level.INTERMEDIATE) boolean AVOID_CBS = true; - public @Option @Visibility(Level.INTERMEDIATE) boolean AVOID_RADIATION = true; - public @Option @Visibility(Level.INTERMEDIATE) boolean USERNAME_ON_TITLE = false; - public @Option @Visibility(Level.ADVANCED) boolean AUTO_REFINE = false; + public @Option + @Visibility(Level.INTERMEDIATE) boolean UPDATE_STATS_WHILE_PAUSED = true; + public @Option + @Visibility(Level.INTERMEDIATE) + @Percentage double DRONE_REPAIR_PERCENTAGE = 0.9; + public @Option + @Visibility(Level.INTERMEDIATE) boolean HONOR_LOST_EXACT = true; + public @Option + @Visibility(Level.INTERMEDIATE) boolean LOG_CHAT = false; + public @Option + @Visibility(Level.INTERMEDIATE) boolean LOG_DEATHS = false; + public @Option + @Visibility(Level.INTERMEDIATE) boolean AVOID_MINES = true; + public @Option + @Visibility(Level.INTERMEDIATE) boolean AVOID_CBS = true; + public @Option + @Visibility(Level.INTERMEDIATE) boolean AVOID_RADIATION = true; + public @Option + @Visibility(Level.INTERMEDIATE) boolean USERNAME_ON_TITLE = false; + public @Option + @Visibility(Level.ADVANCED) boolean AUTO_REFINE = false; } public @Option BotSettings BOT_SETTINGS = new BotSettings(); + public static class BotSettings { public @Option BotGui BOT_GUI = new BotGui(); + public static class BotGui { - @Option @Dropdown(options = LanguageSupplier.class) + @Option + @Dropdown(options = LanguageSupplier.class) public Locale LOCALE = new Locale(Locale.getDefault().getLanguage()); - public @Option @Visibility(Level.INTERMEDIATE) boolean CONFIRM_EXIT = true; - public @Option @Visibility(Level.INTERMEDIATE) boolean SAVE_GUI_POS = false; - public @Option @Visibility(Level.ADVANCED) @Number(min = 1, max = 20, step = 1) int BUTTON_SIZE = 4; + public @Option + @Visibility(Level.INTERMEDIATE) boolean CONFIRM_EXIT = true; + public @Option + @Visibility(Level.INTERMEDIATE) boolean SAVE_GUI_POS = false; + public @Option + @Visibility(Level.ADVANCED) + @Number(min = 1, max = 20, step = 1) int BUTTON_SIZE = 4; public boolean ALWAYS_ON_TOP = true; // No @Option. Edited via button public WindowPosition MAIN_GUI_WINDOW = new WindowPosition(); @@ -230,9 +300,12 @@ public static class WindowPosition { } } - public @Option @Visibility(Level.ADVANCED) APIConfig API_CONFIG = new APIConfig(); + public @Option + @Visibility(Level.ADVANCED) APIConfig API_CONFIG = new APIConfig(); + public static class APIConfig { - public @Option @Dropdown BrowserApi BROWSER_API = OSUtil.getDefaultAPI(); + public @Option + @Dropdown BrowserApi BROWSER_API = OSUtil.getDefaultAPI(); public @Option boolean FULLY_HIDE_API = true; public @Option boolean FORCE_GAME_LANGUAGE = false; public @Option boolean ENFORCE_HW_ACCEL = true; @@ -247,7 +320,9 @@ public static class APIConfig { public GameAPI.Handler.GameQuality gameQuality = GameAPI.Handler.GameQuality.LOW; public transient int transparency = 100, volume = 100; - public @Option @Table @Visibility(Level.DEVELOPER) Map BLOCK_PATTERNS = new HashMap<>(); + public @Option + @Table + @Visibility(Level.DEVELOPER) Map BLOCK_PATTERNS = new HashMap<>(); @Configuration("config.bot_settings.api_config.block_patterns") public static class PatternInfo { @@ -258,53 +333,77 @@ public static class PatternInfo { } public @Option MapDisplay MAP_DISPLAY = new MapDisplay(); + public static class MapDisplay { - @Option @Dropdown(multi = true) + @Option + @Dropdown(multi = true) public Set TOGGLE = EnumSet.of( HERO_NAME, HP_SHIELD_NUM, ZONES, STATS_AREA, BOOSTER_AREA, GROUP_NAMES, GROUP_AREA, SHOW_PET); - public @Option @Visibility(Level.INTERMEDIATE) @Number(max = 300, step = 1) int TRAIL_LENGTH = 15; - public @Option @Visibility(Level.INTERMEDIATE) boolean ROUND_ENTITIES = false; - public @Option @Visibility(Level.INTERMEDIATE) @Percentage double MAP_ZOOM = 1; - public @Option @Visibility(Level.ADVANCED) @Number(min = 1, step = 1, max = 25) int REFRESH_DELAY = 1; - public @Option @Visibility(Level.INTERMEDIATE) boolean MAP_START_STOP = false; - public @Option("colors") @Visibility(Level.ADVANCED) ColorScheme cs = new ColorScheme(); + public @Option + @Visibility(Level.INTERMEDIATE) + @Number(max = 300, step = 1) int TRAIL_LENGTH = 15; + public @Option + @Visibility(Level.INTERMEDIATE) boolean ROUND_ENTITIES = false; + public @Option + @Visibility(Level.INTERMEDIATE) + @Percentage double MAP_ZOOM = 1; + public @Option + @Visibility(Level.ADVANCED) + @Number(min = 1, step = 1, max = 25) int REFRESH_DELAY = 1; + public @Option + @Visibility(Level.INTERMEDIATE) boolean MAP_START_STOP = false; + public @Option("colors") + @Visibility(Level.ADVANCED) ColorScheme cs = new ColorScheme(); } - public @Option @Visibility(Level.INTERMEDIATE) CustomBackground CUSTOM_BACKGROUND = new CustomBackground(); + public @Option + @Visibility(Level.INTERMEDIATE) CustomBackground CUSTOM_BACKGROUND = new CustomBackground(); + public static class CustomBackground { public @Option boolean ENABLED = false; public @Option boolean USE_GAME_BACKGROUND = false; - public @Option @Percentage double OPACITY = 0.3f; + public @Option + @Percentage double OPACITY = 0.3f; public @Option ImageWrapper IMAGE = new ImageWrapper(); } public @Option Performance PERFORMANCE = new Performance(); + public static class Performance { public @Option boolean ALWAYS_ENABLED = true; public @Option("config.bot_settings.api_config.disable_render") boolean DISABLE_RENDER = false; public @Option("config.bot_settings.api_config.max_fps") - @Number(min = 1, max = 60) @Number.Disabled(value = 0, def = 15) int MAX_FPS = 0; + @Number(min = 1, max = 60) + @Number.Disabled(value = 0, def = 15) int MAX_FPS = 0; - public @Option("config.bot_settings.other.min_tick") @Visibility(Level.ADVANCED) - @Number(min = 10, max = 250) @Number.Disabled(value = 15, def = 15) int MIN_TICK = 15; + public @Option("config.bot_settings.other.min_tick") + @Visibility(Level.ADVANCED) + @Number(min = 10, max = 250) + @Number.Disabled(value = 15, def = 15) int MIN_TICK = 15; } - public @Option @Visibility(Level.INTERMEDIATE) Other OTHER = new Other(); + public @Option + @Visibility(Level.INTERMEDIATE) Other OTHER = new Other(); + public static class Other { public @Option boolean SHOW_INSTRUCTIONS = true; public @Option boolean DISABLE_MASTER_PASSWORD = false; public @Option boolean ALWAYS_SHOW_CAPTCHA = false; - public @Option @Number(min = 10, max = 300) int ZONE_RESOLUTION = 30; - public @Option @Visibility(Level.ADVANCED) boolean DEV_STUFF = false; + public @Option + @Number(min = 10, max = 300) int ZONE_RESOLUTION = 30; + public @Option + @Visibility(Level.ADVANCED) boolean DEV_STUFF = false; } } public /*@Option("Extra actions")*/ ExtraActions EXTRA = new ExtraActions(); + public static class ExtraActions { // Dummy testing condition public @Option Condition CONDITION; - public @Option @Table Map ACTION_INFOS = new HashMap<>(); + public @Option + @Table Map ACTION_INFOS = new HashMap<>(); public transient Lazy MODIFIED_ACTIONS = new Lazy.NoCache<>(); } @@ -312,7 +411,17 @@ public static class ShipConfig implements LegacyShipMode { public int CONFIG = 1; public Character FORMATION; + private HeroAPI.Configuration newConfiguration; + private SelectableItem.Formation newFormation; + public ShipConfig() {} + + public ShipConfig(HeroAPI.Configuration configuration, SelectableItem.Formation formation) { + this.newConfiguration = configuration; + this.newFormation = formation; + } + + @Deprecated public ShipConfig(int CONFIG, Character FORMATION) { this.CONFIG = CONFIG; this.FORMATION = FORMATION; @@ -320,31 +429,62 @@ public ShipConfig(int CONFIG, Character FORMATION) { @Override public HeroAPI.Configuration getConfiguration() { - return HeroAPI.Configuration.of(CONFIG); + if (newConfiguration != null) { + CONFIG = newConfiguration.ordinal(); + return newConfiguration; + } + + newConfiguration = HeroAPI.Configuration.of(CONFIG); + return newConfiguration != null ? newConfiguration : HeroAPI.Configuration.FIRST; + } + + public HeroAPI.Configuration getOppositeConfiguration() { + return getConfiguration() == HeroAPI.Configuration.FIRST ? + HeroAPI.Configuration.SECOND : + HeroAPI.Configuration.FIRST; } @Override public SelectableItem.Formation getFormation() { - return ItemUtils.findAssociatedItem(ItemCategory.DRONE_FORMATIONS, FORMATION) - .map(it -> SelectableItem.Formation.of(it.id)) - .orElse(SelectableItem.Formation.STANDARD); + if (newFormation != null) { + FORMATION = ItemUtils.findAssociatedItem(ItemCategory.DRONE_FORMATIONS, newFormation); + return newFormation; + } + // find original keybind to drone formation + if (FORMATION != null) { + newFormation = ItemUtils.findAssociatedItem(ItemCategory.DRONE_FORMATIONS, FORMATION) + .map(item -> SelectableItem.Formation.of(item.id)).orElse(null); + } + // Return new formation if available, or null for "None" + return newFormation; + } + + public ShipMode getShipMode(){ + return ShipMode.of(newConfiguration, newFormation); } @Override public String toString() { - return "Config: " + CONFIG + " Formation: " + CharacterEditor.getDisplay(FORMATION); + return "Config: " + newConfiguration + " " + (newFormation != null ? newFormation.name() : "None"); } @Override public @Nullable Character getLegacyFormation() { return FORMATION; } + + @Override + public boolean isLegacyFormation() { + return FORMATION != null && newFormation == null; + } } public static class PercentRange implements eu.darkbot.api.config.types.PercentRange { public double min, max; - public PercentRange() {} + public PercentRange() { + } + public PercentRange(double min, double max) { this.min = min; this.max = max; 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 990c8be64..095a6bc76 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/NpcInfo.java +++ b/src/main/java/com/github/manolo8/darkbot/config/NpcInfo.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -26,8 +27,11 @@ public class NpcInfo implements eu.darkbot.api.config.types.NpcInfo { public boolean kill; @Option("config.loot.npc_table.attack_key") public Character attackKey; + // @Option("config.loot.npc_table.attack_formation") + @Option.Ignore + private Character attackFormation; @Option("config.loot.npc_table.attack_formation") - public Character attackFormation; + public SelectableItem.Formation attackFormationNew; public ExtraNpcInfo extra = new ExtraNpcInfo(); public transient String name; @@ -54,12 +58,12 @@ public void set(Double radius, Integer priority, Boolean kill, Character attackK set(radius, priority, kill, attackKey, null, extra); } - public void set(Double radius, Integer priority, Boolean kill, Character attackKey, Character attackFormation, ExtraNpcInfo extra) { + public void set(Double radius, Integer priority, Boolean kill, Character attackKey, SelectableItem.Formation 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 (attackFormation != null) this.attackFormationNew = attackFormation; if (extra != null) this.extra = extra; } @@ -68,7 +72,7 @@ public void copyOf(NpcInfo other) { this.priority = other.priority; this.kill = other.kill; this.attackKey = other.attackKey; - this.attackFormation = other.attackFormation; + this.attackFormationNew = other.attackFormationNew; this.extra.flags = new HashSet<>(other.extra.flags); } @@ -109,7 +113,21 @@ public Optional getAmmo() { @Override public Optional getFormation() { - return getHeroItems().getItem(attackFormation, ItemCategory.DRONE_FORMATIONS, SelectableItem.Formation.class); + if (attackFormation != null) { + // Try to find associated formation and update state if found + getHeroItems().getItem(attackFormation, ItemCategory.DRONE_FORMATIONS, SelectableItem.Formation.class) + .map(formation -> { + attackFormation = null; + attackFormationNew = formation; + return formation; + }); + } + return Optional.ofNullable(attackFormationNew); + // If we have a new formation, use it and clear the old one + } + + public void setAttackFormation(SelectableItem.Formation formation) { + this.attackFormationNew = formation; } private static HeroItemsAPI getHeroItems() { @@ -168,7 +186,8 @@ public static String getId(Enum flag) { public static class ExtraNpcInfo { private Set flags = new HashSet<>(); - public ExtraNpcInfo() {} + public ExtraNpcInfo() { + } public ExtraNpcInfo(NpcExtraFlag... flags) { for (NpcExtraFlag flag : flags) set(flag, true); diff --git a/src/main/java/com/github/manolo8/darkbot/config/utils/ItemUtils.java b/src/main/java/com/github/manolo8/darkbot/config/utils/ItemUtils.java index 75494a893..137e5a2d2 100644 --- a/src/main/java/com/github/manolo8/darkbot/config/utils/ItemUtils.java +++ b/src/main/java/com/github/manolo8/darkbot/config/utils/ItemUtils.java @@ -5,6 +5,7 @@ import com.github.manolo8.darkbot.core.objects.slotbars.CategoryBar; import com.github.manolo8.darkbot.core.objects.slotbars.Item; import eu.darkbot.api.game.items.ItemCategory; +import eu.darkbot.api.game.items.SelectableItem; import org.jetbrains.annotations.Nullable; import java.util.Optional; @@ -34,4 +35,10 @@ public static Optional findAssociatedItem(@Nullable ItemCategory category, .findAny(); } + public static Character findAssociatedItem(@Nullable ItemCategory category, SelectableItem selectableItem) { + Main main = Main.INSTANCE; + + return main.facadeManager.slotBars.getKeyBind(selectableItem); + } + } 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 e3eaa1a4a..d13e90b0f 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 @@ -199,10 +199,12 @@ public boolean attackMode(Npc target) { Config.ShipConfig config = this.main.config.GENERAL.OFFENSIVE; boolean otherConfig = target.npcInfo.extra.has(NpcExtra.OPPOSITE_CONFIG); - return setMode( - otherConfig ? ((config.CONFIG % 2) + 1) : config.CONFIG, - target.npcInfo.attackFormation != null ? - target.npcInfo.attackFormation : config.FORMATION); + + HeroAPI.Configuration newConfig = otherConfig ? config.getOppositeConfiguration() : config.getConfiguration(); + + SelectableItem.Formation formation = target.npcInfo.getFormation().orElse(config.getFormation()); + + return setMode(new ShipMode.ShipModeImpl(newConfig, formation)); } public boolean runMode() { @@ -215,7 +217,7 @@ public boolean roamMode() { @Deprecated public boolean setMode(Config.ShipConfig config) { - return setMode(config.CONFIG, config.FORMATION); + return setMode(config.getShipMode()); } @Deprecated diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorProvider.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorProvider.java index e1563ea83..2e19452c4 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorProvider.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/EditorProvider.java @@ -77,6 +77,7 @@ public EditorProvider(PluginAPI api, defaultEditors.put(ShipMode.class, ShipModeEditor.class); defaultEditors.put(Config.ShipConfig.class, ShipModeEditor.class); + defaultEditors.put(ShipMode.ShipModeImpl.class, ShipModeEditor.class); defaultEditors.put(ImageWrapper.class, ImagePicker.class); diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/editors/ShipModeEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/editors/ShipModeEditor.java index 8a1d47937..51bbe123f 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/tree/editors/ShipModeEditor.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/editors/ShipModeEditor.java @@ -2,72 +2,102 @@ import com.github.manolo8.darkbot.config.Config; import com.github.manolo8.darkbot.gui.AdvancedConfig; -import com.github.manolo8.darkbot.gui.tree.utils.SizedLabel; +import com.github.manolo8.darkbot.gui.utils.UIUtils; import eu.darkbot.api.config.ConfigSetting; import eu.darkbot.api.config.types.ShipMode; import eu.darkbot.api.config.util.OptionEditor; +import eu.darkbot.api.game.items.SelectableItem; import eu.darkbot.api.managers.HeroAPI; - -import javax.swing.*; -import java.awt.*; +import net.miginfocom.swing.MigLayout; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JPanel; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class ShipModeEditor extends JPanel implements OptionEditor { - private HeroAPI.Configuration config; + private HeroAPI.Configuration newConfig; private final List configButtons = Arrays.stream(HeroAPI.Configuration.values()) .filter(c -> c != HeroAPI.Configuration.UNKNOWN) - .map(ConfigButton::new).collect(Collectors.toList()); + .map(ConfigButton::new) + .collect(Collectors.toList()); + + private SelectableItem.Formation newFormation; - private final CharacterEditor formationField = new CharacterEditor(); + private final JComboBox comboBox; public ShipModeEditor() { - super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + super(new MigLayout("gap 0, ins 0", "[][]5px[]", "[17px]")); + + this.newConfig = HeroAPI.Configuration.FIRST; + this.newFormation = SelectableItem.Formation.STANDARD; + setOpaque(false); - for (ConfigButton configButton : configButtons) { - add(configButton); - configButton.addKeyListener(formationField); // Relay key presses to formation - } - add(new SizedLabel(" Formation ")); - add(formationField); + configButtons.forEach(button -> add(button, "wmax 17, hmax 17")); + + // Build combo box items: "None" + all formations + Object[] items = new Object[SelectableItem.Formation.values().length + 1]; + items[0] = "None"; + System.arraycopy(SelectableItem.Formation.values(), 0, items, 1, SelectableItem.Formation.values().length); + + comboBox = new JComboBox<>(items); + comboBox.setRenderer(new FormationRenderer()); + + // FIX: Add focus listener to show popup immediately + comboBox.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + comboBox.showPopup(); + } + }); + + comboBox.addItemListener(item -> { + if (item.getStateChange() == ItemEvent.SELECTED) { + Object selected = item.getItem(); + if (selected instanceof SelectableItem.Formation) { + setFormation((SelectableItem.Formation) selected); + } else { + setFormation(null); // "None" selected + } + } + }); + add(comboBox, "hmax 17"); } - @Override - public JComponent getEditorComponent(ConfigSetting mode) { - ShipMode value = mode.getValue(); - + public JComponent getEditorComponent(ConfigSetting shipConfig) { + ShipMode value = shipConfig.getValue(); setConfig(value.getConfiguration()); - - if (value instanceof Config.ShipConfig) { - formationField.setValue(((Config.ShipConfig) value).FORMATION); - } else { - // TODO: show a formation selection dropdown? - // we cannot use old & new formats interchangeably here, because - // converting key to formation or formation to key can only be done while the - // bot is running. If running in no-op mode it's impossible to convert. - // Old format must stay old format not to break plugins, and new format can't be - // expressed with old format because it is unknown before runtime. - // The only real solution is to have different editors for old & new format, - // one that is key based, and one that is formation based. - formationField.setValue(null); - } - + setFormation(value.getFormation()); return this; } private void setConfig(HeroAPI.Configuration config) { - this.config = config; + this.newConfig = config; configButtons.forEach(ConfigButton::repaint); } + private void setFormation(SelectableItem.Formation newFormation) { + this.newFormation = newFormation; + this.comboBox.setSelectedItem(newFormation == null ? "None" : newFormation); + } + @Override public ShipMode getEditorValue() { - return new Config.ShipConfig(config.ordinal(), formationField.getEditorValue()); + return new Config.ShipConfig(newConfig, newFormation); } @Override @@ -94,8 +124,28 @@ private class ConfigButton extends JButton { @Override public boolean isDefaultButton() { - return ShipModeEditor.this.config == config; + return ShipModeEditor.this.newConfig == config; } } -} + private static class FormationRenderer extends DefaultListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + if (value instanceof SelectableItem.Formation) { + SelectableItem.Formation formation = (SelectableItem.Formation) value; + setIcon(UIUtils.getFormationIcon(formation.name().toLowerCase())); + setText(formation.toString()); + } else { + // "None" or any other value + setIcon(UIUtils.getFormationIcon("default")); + setText(String.valueOf(value)); + } + + setIconTextGap(3); + return this; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/manolo8/darkbot/gui/tree/utils/TableHelpers.java b/src/main/java/com/github/manolo8/darkbot/gui/tree/utils/TableHelpers.java index 9718ccd54..01a61d02d 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/tree/utils/TableHelpers.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/tree/utils/TableHelpers.java @@ -4,8 +4,10 @@ import com.github.manolo8.darkbot.config.NpcInfo; import com.github.manolo8.darkbot.gui.utils.MultiTableRowSorter; import com.github.manolo8.darkbot.gui.utils.table.ExtraNpcInfoEditor; +import com.github.manolo8.darkbot.gui.utils.table.FormationNpcInfoEditor; import eu.darkbot.api.config.ConfigSetting; import eu.darkbot.api.config.annotations.Table; +import eu.darkbot.api.game.items.SelectableItem; import eu.darkbot.api.managers.ConfigAPI; import eu.darkbot.api.managers.StarSystemAPI; import org.jetbrains.annotations.Nullable; @@ -76,6 +78,7 @@ public void handle(JTable jTable, JScrollPane jScrollPane, new RowSorter.SortKey(2, SortOrder.ASCENDING), new RowSorter.SortKey(0, SortOrder.DESCENDING))); + jTable.setDefaultEditor(SelectableItem.Formation.class, new FormationNpcInfoEditor()); jTable.setDefaultEditor(NpcInfo.ExtraNpcInfo.class, new ExtraNpcInfoEditor()); } } 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 index df9e295ac..538a51305 100644 --- a/src/main/java/com/github/manolo8/darkbot/gui/utils/UIUtils.java +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/UIUtils.java @@ -44,6 +44,18 @@ public static Icon getIcon(String name) { return getSvgIcon(url, 16, 16); } + public static Icon getFormationIcon(String name) { + URL url = UIUtils.class.getResource("/formations/" + name + ".svg"); + if (url != null) return getSvgIcon(url); + + url = Objects.requireNonNull(UIUtils.class.getResource("/missing.svg")); + return getSvgIcon(url); + } + + private static FlatSVGIcon getSvgIcon(URL url) { + return new FlatSVGIcon(url); + } + private static FlatSVGIcon getSvgIcon(URL url, int width, int height) { return new FlatSVGIcon(url).derive(width, height); } diff --git a/src/main/java/com/github/manolo8/darkbot/gui/utils/table/FormationNpcInfoEditor.java b/src/main/java/com/github/manolo8/darkbot/gui/utils/table/FormationNpcInfoEditor.java new file mode 100644 index 000000000..051319357 --- /dev/null +++ b/src/main/java/com/github/manolo8/darkbot/gui/utils/table/FormationNpcInfoEditor.java @@ -0,0 +1,103 @@ +package com.github.manolo8.darkbot.gui.utils.table; + +import com.github.manolo8.darkbot.gui.utils.PopupMenuListenerAdapter; +import com.github.manolo8.darkbot.gui.utils.UIUtils; +import eu.darkbot.api.game.items.SelectableItem; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JTable; +import javax.swing.SwingUtilities; +import javax.swing.ToolTipManager; +import javax.swing.event.PopupMenuEvent; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class FormationNpcInfoEditor extends TableDelegateEditor { + private static final ToolTipManager TOOLTIPS = ToolTipManager.sharedInstance(); + private final JPopupMenu popupMenu = new JPopupMenu(); + private SelectableItem.Formation selectedFormation; + private int tooltipDelay = -1; + + public FormationNpcInfoEditor() { + super(new JLabel()); + + popupMenu.addPopupMenuListener(new PopupMenuListenerAdapter() { + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + if (tooltipDelay != -1) { + TOOLTIPS.setInitialDelay(tooltipDelay); // Restore original delay + } + } + }); + + // "None" option to clear selection + JMenuItem noneItem = new JMenuItem("None"); + noneItem.addActionListener(e -> { + selectedFormation = null; + delegate.setText("None"); + stopCellEditing(); + }); + popupMenu.add(noneItem); + popupMenu.addSeparator(); + + // Add all formations as selectable menu items with icons + for (SelectableItem.Formation formation : SelectableItem.Formation.values()) { + JMenuItem menuItem = new JMenuItem(formation.toString()); + + // Add icon to menu item + Icon icon = UIUtils.getFormationIcon(formation.name().toLowerCase()); + menuItem.setIcon(icon); + + menuItem.setToolTipText("Select formation: " + formation); + menuItem.addActionListener(new FormationSelectionHandler(formation)); + popupMenu.add(menuItem); + } + } + + @Override + protected void setValue(Object val) { + if (val instanceof SelectableItem.Formation) { + selectedFormation = (SelectableItem.Formation) val; + delegate.setText(selectedFormation.name()); + } else { + selectedFormation = null; + delegate.setText("None"); + } + } + + @Override + protected Object getValue() { + return selectedFormation; + } + + public void startEditing(JTable table, Object value, boolean isSelected, int row, int column) { + SwingUtilities.invokeLater(() -> { + if (!table.isShowing()) return; + + Rectangle cellRect = table.getCellRect(row, column, false); + int x = cellRect.x; + int y = cellRect.y + cellRect.height; + + popupMenu.show(table, x, y); + }); + } + + private class FormationSelectionHandler implements ActionListener { + private final SelectableItem.Formation formation; + + public FormationSelectionHandler(SelectableItem.Formation formation) { + this.formation = formation; + } + + @Override + public void actionPerformed(ActionEvent e) { + selectedFormation = formation; + delegate.setText(formation.toString()); + stopCellEditing(); + } + } +} diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store new file mode 100644 index 000000000..9f623fb4b Binary files /dev/null and b/src/main/resources/.DS_Store differ diff --git a/src/main/resources/formations/arrow.svg b/src/main/resources/formations/arrow.svg new file mode 100644 index 000000000..2838d8b8c --- /dev/null +++ b/src/main/resources/formations/arrow.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/barrage.svg b/src/main/resources/formations/barrage.svg new file mode 100644 index 000000000..e21922eb6 --- /dev/null +++ b/src/main/resources/formations/barrage.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/bat.svg b/src/main/resources/formations/bat.svg new file mode 100644 index 000000000..18030726e --- /dev/null +++ b/src/main/resources/formations/bat.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/chevron.svg b/src/main/resources/formations/chevron.svg new file mode 100644 index 000000000..37795b714 --- /dev/null +++ b/src/main/resources/formations/chevron.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/crab.svg b/src/main/resources/formations/crab.svg new file mode 100644 index 000000000..3ecee65df --- /dev/null +++ b/src/main/resources/formations/crab.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/default.svg b/src/main/resources/formations/default.svg new file mode 100644 index 000000000..6d9e0b20b --- /dev/null +++ b/src/main/resources/formations/default.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/formations/diamond.svg b/src/main/resources/formations/diamond.svg new file mode 100644 index 000000000..b04af7450 --- /dev/null +++ b/src/main/resources/formations/diamond.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/dome.svg b/src/main/resources/formations/dome.svg new file mode 100644 index 000000000..a9d8e0a16 --- /dev/null +++ b/src/main/resources/formations/dome.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/double_arrow.svg b/src/main/resources/formations/double_arrow.svg new file mode 100644 index 000000000..e1a7c74c4 --- /dev/null +++ b/src/main/resources/formations/double_arrow.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/drill.svg b/src/main/resources/formations/drill.svg new file mode 100644 index 000000000..1c027d122 --- /dev/null +++ b/src/main/resources/formations/drill.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/heart.svg b/src/main/resources/formations/heart.svg new file mode 100644 index 000000000..6b5912509 --- /dev/null +++ b/src/main/resources/formations/heart.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/lance.svg b/src/main/resources/formations/lance.svg new file mode 100644 index 000000000..1be7f8e04 --- /dev/null +++ b/src/main/resources/formations/lance.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/mosquito.svg b/src/main/resources/formations/mosquito.svg new file mode 100644 index 000000000..01668b229 --- /dev/null +++ b/src/main/resources/formations/mosquito.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/moth.svg b/src/main/resources/formations/moth.svg new file mode 100644 index 000000000..b61f792ca --- /dev/null +++ b/src/main/resources/formations/moth.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/pincer.svg b/src/main/resources/formations/pincer.svg new file mode 100644 index 000000000..8c3c58fa0 --- /dev/null +++ b/src/main/resources/formations/pincer.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/ring.svg b/src/main/resources/formations/ring.svg new file mode 100644 index 000000000..41d548fbd --- /dev/null +++ b/src/main/resources/formations/ring.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/standard.svg b/src/main/resources/formations/standard.svg new file mode 100644 index 000000000..34f9ee2ff --- /dev/null +++ b/src/main/resources/formations/standard.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/star.svg b/src/main/resources/formations/star.svg new file mode 100644 index 000000000..dea374305 --- /dev/null +++ b/src/main/resources/formations/star.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/turtle.svg b/src/main/resources/formations/turtle.svg new file mode 100644 index 000000000..fa339b5f0 --- /dev/null +++ b/src/main/resources/formations/turtle.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/veteran.svg b/src/main/resources/formations/veteran.svg new file mode 100644 index 000000000..2211f4657 --- /dev/null +++ b/src/main/resources/formations/veteran.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/wavy.svg b/src/main/resources/formations/wavy.svg new file mode 100644 index 000000000..0ba399b8e --- /dev/null +++ b/src/main/resources/formations/wavy.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/wheel.svg b/src/main/resources/formations/wheel.svg new file mode 100644 index 000000000..1ef46233f --- /dev/null +++ b/src/main/resources/formations/wheel.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/x.svg b/src/main/resources/formations/x.svg new file mode 100644 index 000000000..b72ec4b5f --- /dev/null +++ b/src/main/resources/formations/x.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/formations/x2.svg b/src/main/resources/formations/x2.svg new file mode 100644 index 000000000..01668b229 --- /dev/null +++ b/src/main/resources/formations/x2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file