Traum Posted January 28, 2016 Share Posted January 28, 2016 (edited) [Tutorial] How to pass data from you GUI to your script Feel free to make suggestions / ask questions. 1. Create a DATA class - This class is going to hold all the data and variables modifiable by the end user. Example variables: String monsterName; String eatingThreshold; boolean useAntipoison; Position monsterCoordinates; Roles: - The variables need to be usable by our script (a different class), in order to enable access we can provide getter methods (safe) or just change the visibility of the variables (lazy). - The variables need to be modifiable by our GUI (again, a different class), in order to enable modification we can provide setter methods (safe) or just change the visibility of the variables (lazy). Example code: // This is a simple example data class for a fighter script. public class FighterData { // I'm not using getters / setters here to reduce verbosity, you should though. public String monsterName; public int eatThreshold; public boolean logWhenDied; public FighterData(String monsterName, int eatThreshold, boolean logWhenDied) { this.monsterName = monsterName; this.eatThreshold = eatThreshold; this.logWhenDied = logWhenDied; } } 2. Add an instance of your DATA class to your script @ScriptManifest(author = "Me", info = "", logo = "", name = "Fighter Script", version = 0) public class MyScript extends Script { private FighterData data = new FighterData("Goblin", 20, true); @Override public int onLoop() throws InterruptedException { if (myPlayer().getHealth() < data.eatThreshold) { // Eat. } return 200; } } So far all we have done is take the global variables from your script class (traditionally speaking) and moved them to their own separate class. QUESTION: Can't I just make my global script variables public (or provide getters and setter to them) and pass my script instance to the GUI? ANSWER: You could. Here are some arguments for using a separate class: - It will make your life much easier when you decide to implement importation and exportation of the data. - You can store multiple sets of data for later use easily and cleanly (for data-based task scheduling for example). - By encapsulating the data you can easily and without too much code, provide preset values for that data. Compare: public static final FighterData GOBLIN_SUICIDER = new FighterData("Goblin", -1, false); public static final FighterData DRUID_TANK = new FighterData("Druid", 20, true); public static final FighterData SPIDER_BOSS = new FighterData("Spider", 5, true); public static final FighterData ROCK_CRAB_SENSEI = new FighterData("Rock Crab", 6, true); public static final FighterData UNICORN_PRO = new FighterData("Unicorn", 9, true); private FighterData profile = GOBLIN_SUICIDER; vs: private String monsterName; private int eatThreshold; private boolean logWhenDied; public void build(String preset) { // We are just typing our data class's constructor code over and over again... switch (preset) { case "GOBLIN_SUICIDER": monsterName = "Goblin" eatThreshold = -1; logWhenDied = false; break; case "DRUID_TANK": monsterName = "Druid" eatThreshold = 20; logWhenDied = true; break; case "SPIDER_BOSS": monsterName = "Spider" eatThreshold = 5; logWhenDied = true; break; } case "ROCK_CRAB_SENSEI": monsterName = "Rock Crab" eatThreshold = 6; logWhenDied = true; break; } case "UNICORN_PRO": monsterName = "Unicorn" eatThreshold = 9; logWhenDied = true; break; } } build("GOBLIN_SUICIDER"); - Etcetera 3. Time for the GUI / VIEW code Current implementation: DATA OBJECT -> SCRIPT Desired implementation: GUI -> DATA OBJECT -> SCRIPT (The GUI and the SCRIPT share the DATA OBJECT). QUESTION: How do we share the data object between the SCRIPT class and the GUI class? ANSWER: Just pass a reference to the data object instance to the GUI (via a constructor for example). SCRIPT: package tutorial; import org.osbot.rs07.api.model.NPC; import org.osbot.rs07.script.Script; import org.osbot.rs07.script.ScriptManifest; @ScriptManifest(author = "Me", info = "", logo = "", name = "Fighter Script", version = 0) public class MyScripty extends Script { private FighterData data = new FighterData("Goblin", 20, true); private FighterGUI gui = new FighterGUI(data); @Override public void onStart() throws InterruptedException { super.onStart(); gui.setVisible(true); //... } private NPC target = null; private boolean died = false; @Override public int onLoop() throws InterruptedException { if (myPlayer().getHealth() < data.eatThreshold) { // Eat. } if (died && data.logWhenDied) { // Log out. } else { target = getNpcs().closest(data.monsterName); // Attack. } return 200; } } GUI: import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class FighterGUI extends JFrame { private static final long serialVersionUID = -1969829387256339229 L; private FighterData data; public FighterGUI(FighterData model) { super(); this.data = model; SwingUtilities.invokeLater(new Runnable() { public void run() { setTitle("Fighter Script"); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setResizable(false); populate(); pack(); setLocationRelativeTo(null); } }); } private void populate() { JPanel cp = new JPanel(); setContentPane(cp); JTextField name = new JTextField("monster name"); cp.add(name); JButton nameok = new JButton("ok"); nameok.addActionListener((al) -> data.monsterName = name.getText()); cp.add(nameok); JTextField eat = new JTextField("eat threshold"); cp.add(eat); JButton eatok = new JButton("ok"); nameok.addActionListener((al) -> data.eatThreshold = Integer.parseInt(eat.getText())); // PLEASE DO NOT FORGET TO ADD INTEGER PARSING ERROR HANDLING HERE! cp.add(eatok); JCheckBox log = new JCheckBox("log after death"); log.addActionListener((al) -> data.logWhenDied = log.isSelected()); cp.add(log); } } Edited January 31, 2016 by Traum 1 Quote Link to comment Share on other sites More sharing options...
lisabe96 Posted January 28, 2016 Share Posted January 28, 2016 (edited) public static final FighterData GOBLIN_SUICIDER = new FighterData("Goblin", -1, false); public static final FighterData DRUID_TANK = new FighterData("Druid", 20, true); public static final FighterData SPIDER_BOSS = new FighterData("Spider", 5, true); public static final FighterData ROCK_CRAB_SENSEI = new FighterData("Rock Crab", 6, true); public static final FighterData UNICORN_PRO = new FighterData("Unicorn", 9, true); Not a fan of this, especially when you're getting big amounts of objects. Using a map or list to store the objects would be a better approach imo. //DataManager private static Map<EnumExample, DataObject> data = new HashMap<>(); public static void load() { data.add(EnumExample.GOBLIN, new DataObject(#, #)); } public static DataObject find(EnumExample type) { return data.get(type); } //Main @Override public void onStart() { DataManager.load(); } But that's just my 2 cents. For the rest great guide! Sure a lot of people will learn a lot from this Edited January 28, 2016 by lisabe96 Quote Link to comment Share on other sites More sharing options...
Traum Posted January 28, 2016 Author Share Posted January 28, 2016 public static final FighterData GOBLIN_SUICIDER = new FighterData("Goblin", -1, false); public static final FighterData DRUID_TANK = new FighterData("Druid", 20, true); public static final FighterData SPIDER_BOSS = new FighterData("Spider", 5, true); public static final FighterData ROCK_CRAB_SENSEI = new FighterData("Rock Crab", 6, true); public static final FighterData UNICORN_PRO = new FighterData("Unicorn", 9, true); Not a fan of this, especially when you're getting big amounts of objects. Using a map or list to store the objects would be a better approach imo. //DataManager private static Map<EnumExample, DataObject> data = new HashMap<>(); public static void load() { data.add(EnumExample.GOBLIN, new DataObject(#, #)); } public static DataObject find(EnumExample type) { return data.get(type); } //Main @Override public void onStart() { DataManager.load(); } But that's just my 2 cents. For the rest great guide! Sure a lot of people will learn a lot from this Oh yes absolutely, I just wanted to point out the verbosity when not making the data composite. Quick tip: when using enums as key values in a map, consider choosing an EnumMap over a HashMap! 1 Quote Link to comment Share on other sites More sharing options...
lisabe96 Posted January 28, 2016 Share Posted January 28, 2016 Another thing. Your monsters are not variable as you're creating objects for each type of monster. So it's possible that the monster the player enters, doesn't actually exists in our data. So you would have to store the available ones in a comboBox probably to avoid this. Unless I've misinterpreted your code Quote Link to comment Share on other sites More sharing options...
Traum Posted January 28, 2016 Author Share Posted January 28, 2016 Another thing. Your monsters are not variable as you're creating objects for each type of monster. So it's possible that the monster the player enters, doesn't actually exists in our data. So you would have to store the available ones in a comboBox probably to avoid this. Unless I've misinterpreted your code I think you did misinterpret it, but that's probably my fault: The list of presets is not part of the provided script-gui implementation, it's simply there to demonstrate an extra benefit of having a unifying data structure . Also, my GUI example is very minimal and not very thorough, I just quickly wrote it up to be able to demonstrate data passing (the actual subject of the tutorial) . 1 Quote Link to comment Share on other sites More sharing options...