[Save Manager] FTL Homeworld AE -v3.1

Distribute and discuss mods that are functional. Moderator - Grognak
Vhati
Posts: 792
Joined: Thu Oct 25, 2012 12:01 pm

Re: [Program] FTL Homeworld

Postby Vhati » Sat Jul 27, 2013 8:55 pm

kartoFlane wrote:
iceburg333 wrote:The problem is, in "if (SpaceDockUI.currentShip != this"), this is an ActionListener

Well, there's a very simple way to work around that. And it only requires a single private variable, set to a certain object...
In case you couldn't figure it out, check this.

Another way to get the outer class's "this" from within an inner class is...

Code: Select all

if (SpaceDockUI.currentShip != ShipSave.this)

Though if I'm doing a lot of outer-self-referencing, I tend to do the "self" private variable trick, or just give the outer class an actionPerformed method (ShipSave extends ... implements ActionListener).

...

I haven't looked at the code here, but it may be preferable to move the click logic into the class that is constructing all the clickable things. Create a single shared ActionListener there, create each clickable thing, and add the listener, as part of filling the GUI.

For instance a panel with lots of buttons. The panel creates a listener (or itself implements actionPerformed). The panel creates buttons, adds the buttons to itself (the panel), and adds that same listener to all the buttons. When a button is clicked, the listener determines which one specifically got clicked via the ActionEvent's getSource() method (then doing an instanceof check and casting as appropriate).
iceburg333
Posts: 67
Joined: Tue Jun 25, 2013 8:52 pm

Re: [Program] FTL Homeworld

Postby iceburg333 » Sat Jul 27, 2013 10:38 pm

kartoFlane wrote:Well, there's a very simple way to work around that. And it only requires a single private variable, set to a certain object...
In case you couldn't figure it out, check this.

The private Self Variable is genius, but now I'm having trouble with accessing the SpacedockUI pane (static, nonstatic troubles). Let me work through Vhati's post and get back to you guys.

KartoFlane wrote:Huh. True. Though I still think it's worth using an ArrayList instead, simply due to the ease of use.

I started changing it, but decided not to. All of the functions work fine now, and I think changing it now would only obfuscate it since a simple Array does everything it needs to do... Because I use a ton of myShips[i], I would need to cast it to an array anyhow, and why do that when I can have it start as an array...

KartoFlane wrote:I see. To get around that and allow the program to parse all savegames, you could use File.listFiles() to get an array of all files in the directory. Then check if they contain "continue", and end with ".sav". Or just check for ".sav" extension, to get custom-named savegames too:

Smart, I had that already, and so I just needed to point to it. It now takes an int (length) and checks to make sure the number is never 2 higher than the number of save files, so I have room to create a new ship while making sure the while doesn't go on forever.

Code: Select all

public static boolean dockShip(ShipSave ss1, int listLength) {
...
while (oldFile.exists() && i <= (listLength + 2)) {


----------------------------
Vhati:
This is my original code in my Spacedock UI:

Code: Select all

class BoardListener implements ActionListener {
      public void actionPerformed(ActionEvent ae) {            
         //connect the button to the proper ship (there must be a better way to do this!)
         //Well, this is much better than the old for loop, anyway
         JButton sourceButton = (JButton) ae.getSource();
         int i = Arrays.asList(buttonList).indexOf(sourceButton);
         if (sourceButton.getText().equals("Dock")) {
             sourceButton.setText("Board");             
             ShipSave.dockShip(myShips[i], myShips.length);
             currentShip = null;
             
         } else if (sourceButton.getText().equals("Board")) {
             sourceButton.setText("Dock");
             //if they have boarded a ship, dock it before boarding new one;
             if  (currentShip != null) {
                //Find which ship has the file, dock it, and then update it's button
               // System.out.println("Already manning a ship!");
                ShipSave.dockShip(currentShip, myShips.length);
                int b = Arrays.asList(myShips).indexOf(currentShip);
                buttonList[b].setText("Board");
                currentShip = null;
             } 
             ShipSave.boardShip(myShips[i]);
             currentShip = myShips[i];
             
         }
      }
   }

My problem is that in the SpacedockUI I can't access the ShipSave that the button belongs to as a field, but if I move it into the ShipSave, I can't access the specific SpacedockUI that I need. So I'm using a buttonlist[] to use indexof to match with the index of myShips[]. I basically need to be able to access the specific object that contains (as a field) the object that I have.

EDit: Github updated in case you want to see the whole code.
Image
Vhati
Posts: 792
Joined: Thu Oct 25, 2012 12:01 pm

Re: [Program] FTL Homeworld

Postby Vhati » Sat Jul 27, 2013 11:05 pm

iceburg333 wrote:I'm using a buttonlist[] to use indexof to match with the index of myShips[].

At the top of Spacedock UI, declare a lookup table:
HashMap<JButton,ShipSave> btnToShipMap

As you create the buttons:
btnToShipMap.put(someBtn, someShip);

In the listener:

Code: Select all

Object source = ae.getSource();
if (source instanceof JButton) {
  JButton sourceButton = (JButton)source;
  ShipSave myShip = btnToShipMap.get(sourceButton);
}
Might do a btnToShipMap.containsKey(sourceButton) check, too, if you'd like a universal listener that also handles buttons that aren't related to ShipSaves.

As in, the thing that was clicked was a button, was it one of the ship buttons, okay look up the ShipSave.
Last edited by Vhati on Sat Jul 27, 2013 11:32 pm, edited 6 times in total.
Vhati
Posts: 792
Joined: Thu Oct 25, 2012 12:01 pm

Re: [Program] FTL Homeworld

Postby Vhati » Sat Jul 27, 2013 11:17 pm

iceburg333 wrote:
KartoFlane wrote:Huh. True. Though I still think it's worth using an ArrayList instead, simply due to the ease of use.

I started changing it, but decided not to. All of the functions work fine now, and I think changing it now would only obfuscate it since a simple Array does everything it needs to do... Because I use a ton of myShips[i], I would need to cast it to an array anyhow, and why do that when I can have it start as an array...

ArrayLists do everything arrays can and more.*
Search/replace "myShips[i]" with "myShips.get(i)"
and "myShips.length" with "myShips.size()"

Every time you do Arrays.asList(myShips) you're basically creating an ArrayList.
ArrayLists have a built-in method for indexOf().


* With the exception of holding primitives, which aren't objects. An ArrayList would need to hold Integer or Boolean objects (note the capitals), which happen to contain primitive values.
iceburg333
Posts: 67
Joined: Tue Jun 25, 2013 8:52 pm

Re: [Program] FTL Homeworld

Postby iceburg333 » Sat Jul 27, 2013 11:44 pm

Vhati wrote:At the top of Spacedock UI, declare a lookup table:
HashMap<JButton,ShipSave> btnToShipMap

As you create the buttons:
btnToShipMap.put(someBtn, someShip);

In the listener:

Code: Select all

Object source = ae.getSource();
if (source instanceof JButton) {
  JButton sourceButton = (JButton)source;
  ShipSave myShip = btnToShipMap.get(sourceButton);
}

That worked! And I guess I could see how that would be a lot better than using the index method I was using. Thank you guys!

Vhati wrote:Might do a btnToShipMap.containsKey(sourceButton) check, too, if you'd like a universal listener that also handles buttons that aren't related to ShipSaves.

As in, the thing that was clicked was a button, was it one of the ship buttons, okay look up the ShipSave.

Okay, I'll do that next, combine all of my listeners for SpacedockUI into one big listener...

Vhati wrote:ArrayLists pretty much do everything arrays can and more.*
Search/replace "myShips[i]" with "myShips.get(i)"

Every time you do Arrays.asList(myShips) you're basically creating an ArrayList.
ArrayLists have a built-in method for indexOf().


* With the exception of holding primitives, which aren't objects. An ArrayList would need to hold Integer or Boolean objects (note the capitals), which happen to contain primitive values.

Fine! If both of you think that I should use ArrayLists then I'll switch over, lol. Thanks, for the search and replace tip, I haven't used ArrayLists enough to have known that, so I was thinking it was more complex than that. I'll get on that as soon as I combine the listeners. :)
Image
Vhati
Posts: 792
Joined: Thu Oct 25, 2012 12:01 pm

Re: [Program] FTL Homeworld

Postby Vhati » Sat Jul 27, 2013 11:55 pm

From SpaceDockUI.java:

Code: Select all

buttonList[i].addActionListener(new BoardListener());


You don't need to create separate listener objects dedicated to each button, btw.
That listener class's actionPerformed knows how to figure out the source on its own, as each event occurs.

Code: Select all

BoardListener boardListener = new BoardListener();
/* for loop */ {
  buttonList[i].addActionListener(boardListener);
}
iceburg333
Posts: 67
Joined: Tue Jun 25, 2013 8:52 pm

Re: [Program] FTL Homeworld

Postby iceburg333 » Sun Jul 28, 2013 12:32 am

Vhati wrote:From SpaceDockUI.java:

Code: Select all

buttonList[i].addActionListener(new BoardListener());


You don't need to create separate listener objects dedicated to each button, btw.
That listener class's actionPerformed knows how to figure out the source on its own, as each event occurs.

Code: Select all

BoardListener boardListener = new BoardListener();
/* for loop */ {
  buttonList[i].addActionListener(boardListener);
}


Oh cool! I thought I had to make a new listener for each one. I've changed it to make just one listener and use it for all cases, plus I merged the listeners, converted myShips to an ArrayList, and implemented the hashmap for the boardbuttons. Today got a bit sketchy in parts, and so I'm stoked to have everything working again.
Being 'self-taught' in java/ modding, I have learned to really appreciate when there are people like you guys who are willing to pick apart your code and then help you make it better/help you avoid bad habits/help you improve and learn. I really do appreciate both of you guys taking the time to help me. =D
So thanks!
Ice
----
Updated Github
Image
Vhati
Posts: 792
Joined: Thu Oct 25, 2012 12:01 pm

Re: [Program] FTL Homeworld

Postby Vhati » Sun Jul 28, 2013 3:08 am

iceburg333 wrote:I've changed it to make just one listener and use it for all cases, plus I merged the listeners

If you're only ever gonna have one listener, might as well ditch the inner class and put an actionPerformed() method on the panel itself. 8-)

Code: Select all

public class SpaceDockUI extends JPanel implements ActionListener {
//...
    launchbtn = new JButton("Launch FTL");
    launchbtn.addActionListener(this);
//...
  public void actionPerformed(ActionEvent ae) {
Implementing an interface is simply a promise that the class has particular methods.



Sidenote for when you're doing comparisons to identify the event's source:

To test whether the objects referenced by two variables have similar values: a.equals(b)
To test whether two variables both reference the same object: a == b

Technically an object is always similar to itself, but it can also be similar to other objects.

...

Why the init()?
Granted, sometimes an object may have several constructors that need to share code, so they each do their special stuff, then call a private init()...

Then again, I'm vaguely aware of some sort of potential weirdness with listeners in constructors (exceptions could risk leaking "this" when "this" isn't fully constructed yet), but that isn't resolved by calling an init() from within the constructor. I've not researched that much, but if I were worried I'd do this.

Code: Select all

SpaceDockUI obj = new SpaceDockUI();
obj.init();
I never cared because if some catastrophe occurred in a panel's constructor, usually the only things that were tied to "this" would be inside the panel, which also wouldn't be visible yet to cause mischief when clicked.
A constructor that added "this" to a pre-existing registry/collection/global-variable would be bad (something might want to interact with "this" before "this" is ready).

Or I'm overthinking the question. :P
iceburg333
Posts: 67
Joined: Tue Jun 25, 2013 8:52 pm

Re: [Program] FTL Homeworld

Postby iceburg333 » Sun Jul 28, 2013 1:45 pm

Vhati wrote:If you're only ever gonna have one listener, might as well ditch the inner class and put an actionPerformed() method on the panel itself. 8-)

Wow, that's crazy! (I never would have thought to implement "this" and so would have thought it impossible). I think that that should be my only listener for that panel/UI. I implemented it and it now works perfectly. Thanks! :D


Vhati wrote:Sidenote for when you're doing comparisons to identify the event's source:

To test whether the objects referenced by two variables have similar values: a.equals(b)
To test whether two variables both reference the same object: a == b

Technically an object is always similar to itself, but it can also be similar to other objects.

Hmm. KartoFlane was doing it properly in the example code he sent me too, but I guess the tutorial I had watched had gotten me confused. I think that that makes sense though. I went back and corrected the code.

Vhati wrote:Why the init()?
Granted, sometimes an object may have several constructors that need to share code, so they each do their special stuff, then call a private init()...

Then again, I'm vaguely aware of some sort of potential weirdness with listeners in constructors (exceptions could risk leaking "this" when "this" isn't fully constructed yet), but that isn't resolved by calling an init() from within the constructor. I've not researched that much, but if I were worried I'd do this.

Code: Select all

SpaceDockUI obj = new SpaceDockUI();
obj.init();
I never cared because if some catastrophe occurred in a panel's constructor, usually the only things that were tied to "this" would be inside the panel, which also wouldn't be visible yet to cause mischief when clicked.
A constructor that added "this" to a pre-existing registry/collection/global-variable would be bad (something might want to interact with "this" before "this" is ready).

Or I'm overthinking the question. :P

Hey, overthink the question as much as you like! I learn by asking questions and by overthinking stuff (understanding whys and hows really help me; the more info the better!)
Originally I had no init, but the class is run from HomeworldFrame (the top level UI) which is run by FTLHomeworld (main class). I wanted to make it like a game launcher: you pull up FTL Homeworld, select your ship, press Launch FTL to start the game, play, and then when you're done playing that ship, save and quit. FTL Homeworld is still up on your desktop, hit the refresh button to see the changes in your ships, and then you can dock and pick a new ship to play.
So, I needed a button to refresh the ship select button. By making the whole SpaceDockUI run off of an init() that removes everything before it constructs the tab, all I had to do to refresh the tab was run init().
Does that make sense/ is there a better way?
Thanks again!
I'm going to keep working on my cargobay tab (the next one I want to release), but if either of you guys spots more code I could do better, let me know, I appreciate the advice!
Image
iceburg333
Posts: 67
Joined: Tue Jun 25, 2013 8:52 pm

Re: [Program] FTL SpaceDock

Postby iceburg333 » Sun Jul 28, 2013 9:01 pm

------
Latest Progress:
I'm working on cargo bay, which will let you trade items between ships or store them in "Space dock storage". To store/save the items in storage, I'm creating a dummy save file "Homeworld.sav". My idea is that a ship can store unlimited drones, crew, weapons, augments, scrap, etc so long as it isn't used in game... So I just need to create a fake ship that will never be seen so that I can store items in it.

Code: Select all

public class FTLHomeworld {
...
if ( homeworld_save == null ) {
         showErrorDialog( "Homeworld.sav was not found.\nFTL Homeworld will create one in the save folder" );
         // log.debug( "No FTL dats path found, exiting." );
         //TODO Create new Homeworld.sav
         SavedGameParser parser = new SavedGameParser();
         File homeworldFile = new File(save_location + "\\Homeworld.sav");
         //OutputStream out = null;
         SavedGameState homeSave = new SavedGameState();
         homeSave.setPlayerShipState(new ShipState("Spacedock Storage", "PLAYER_SHIP_EASY", "kestral", "kestral", false));
         DoorState ds = new DoorState(false, false);
//         for (homeSave.getPlayerShipState().getDoorMap() : ) {
//            
//         }
//         homeSave.getPlayerShipState().getDoorMap().put(key, ds);
         try {
            out = new FileOutputStream(homeworldFile);
            parser.writeSavedGame(out, homeSave);
            //write the new save to config
            homeworld_save = homeworldFile;
            config.setProperty( "HomeworldSave", homeworld_save.getAbsolutePath() );
            writeConfig = true;
            // log.info( "FTL saves located at: " + save_location.getAbsolutePath() );
         } catch (FileNotFoundException e) {
            // Auto-generated catch block
            e.printStackTrace();
         } catch (IOException e) {
            // Auto-generated catch block
            e.printStackTrace();
         } finally {
            if ( out != null ) { try { out.close(); } catch (IOException e) {e.printStackTrace();} }
         }

I'm having trouble creating and writing a new file, since the constructers for SavedGameState and ShipState don't necessarily fill all of the fields.

I'm specifically having trouble with:

Code: Select all

public void writeDoor( OutputStream out, DoorState door ) throws IOException {
      writeBool( out, door.isOpen() );
      writeBool( out, door.isWalkingThrough() );
   }

In writeDoor, the DoorState door is null, and I'm having trouble figuring out how to construct my dummy save so that it's not null. I am looking into better understanding hashmaps. My current theory is that the parser is reading the kestrel blueprint and expecting more doors than their are, so I'm going to try to reverse engineer it to have the doors it expects... :geek:


class on Github