Vhati / ftl-profile-editor

Profile editor for FTL: Faster Than Light (www.ftlgame.com)
GNU General Public License v2.0
128 stars 30 forks source link

Get/set the name and type of the current sector #66

Closed Niels-NTG closed 9 years ago

Niels-NTG commented 9 years ago

Currently it doesn't seem to be possible to retrieve and change the name of the sector the ship is currently in. Information about the name and type (friendly, hostile, nebula) has to be saved somewhere in continue.sav. Looking trough SavedGameParser.java it seems that ftl-profile-editor doesn't have any methods of getting or setting this information.

I'm overlooking something or is it just not possible (yet)?

Vhati commented 9 years ago

Information about the name and type (friendly, hostile, nebula) has to be saved somewhere in continue.sav.

I applaud your diligence, but unfortunately it isn't there.

The saved game only contains the Nth sector you're in... and a seed integer, used in FTL at runtime, to reinitialize the OS's random number generator, which then re-generates the sector tree, populating randomly sized columns with random sector dots, from a list defined inside FTL's resources (This editor or Slipstream can "Extract Dats" to let you examine them), under /data/sector_data.xml... and a flat list of T/F visitation breadcrumbs (been here, not here), which only make sense if the elements of the list are applied to every dot in the tree, continuing down each column and wrapping vertically.

I had to do some serious reverse engineering just to display the sector tree in the editor. You can navigate it; you can scramble it; you can peek at the dots; but you can't edit the dots.

In other words, to change a sector name/type, you have to create a MOD that edits that xml. It will not be possible to tweak the name/type within the scope of a single campaign using custom saved game. . . From your repository, I see you're the ambitious sort... ; )

To merely read the name, you have to get the current sector index and sector tree seed from continue.sav, construct a RandomSectorTreeGenerator (with an OS-specific RNG, or 'native'), to get a list of columns of SectorDots. With that, you can construct a SectorTree to query the last visited col/dot, or loop backward over the columns yourself testing "visited" status. When you have the SectorDot you want, you can call getTitle().

Relevant packages: net.blerf.ftl.parser.random net.blerf.ftl.parser.sectortree net.blerf.ftl.model.sectortree

Vhati commented 9 years ago

Wait, I forgot to apply the breadcrumbs to the freshly generated columns. This should be close.

The instance way

RandomSectorTreeGenerator myGen = new RandomSectorTreeGenerator( new NativeRandom() );
SectorTree = new SectorTree( myGenerator.generateSectorTree( gameState.getSectorTreeSeed(), gameState.isDLCEnabled() ) );
myTree.setSectorVisitation( gameState.getSectorVisitation() );
SectorDot currentDot = myTree.getVisitedDot( ( getLastVisitedColumn() );

The static way

RandomSectorTreeGenerator myGen = new RandomSectorTreeGenerator( new NativeRandom() );
List<List<SectorDot>> myColumns = myGenerator.generateSectorTree( gameState.getSectorTreeSeed(), gameState.isDLCEnabled() );
SectorTree.setVisitation( myColumns, gameState.getSectorVisitation() );

Then do a reverse "for" loop over myColumns and their dots until isVisited() returns true. Only one dot in a column is supposed to be true (unless something went wrong building the tree).

Vhati commented 9 years ago

Doh! Since we already know the current sector number, this way saves a loop and ignores breadcrumbs. Of course, the shortcut assumes you only care about finding that one dot.

RandomSectorTreeGenerator myGen = new RandomSectorTreeGenerator( new NativeRandom() );
List<List<SectorDot>> myColumns = myGenerator.generateSectorTree( gameState.getSectorTreeSeed(), gameState.isDLCEnabled() );
List<SectorDot> visitedColumn = myColumns.get( gameState.getSectorNumber() );
SectorDot currentDot = null;
for ( SectorDot dot : visitedColumn ) {
    if ( dot.isVisited() ) {
        currentDot = dot;
        break;
    }
}
System.out.println( currentDot.getTitle() );
Niels-NTG commented 9 years ago

It took me a while to figure it out, mainly because SectorDot .isVisited() always returns false, regardless of the ship has been there or not. So I can up with the following clunky solution:

    RandomSectorTreeGenerator myGen = new RandomSectorTreeGenerator( new NativeRandom() );
    List<List<SectorDot>> myColumns = myGen.generateSectorTree( gameState.getSectorTreeSeed(), gameState.isDLCEnabled() );
    List<SectorDot> visitedColumn = myColumns.get( gameState.getSectorNumber() );

    int visitedColumnOffset = 0;
    for (int i = 0; i < currentGameState.getSectorNumber(); i++) {
        visitedColumnOffset += myColumns.get(i).size();
    }
    List<Boolean> visitedColumnStatus = gameState.getSectorVisitation().subList(visitedColumnOffset, visitedColumnOffset + visitedColumn.size());

    SectorDot currentDot = null;

    for (int i = 0; i < visitedColumn.size(); i++) {
        if ( visitedColumnStatus.get(i) ) {
            currentDot = visitedColumn.get(i);
            break;
        }
    }

    log.info( "Sector Title : " + currentDot.getTitle() );

Thanks for the help.

And yes, the main reason for asking this was that I want to use this for my own project. Modifying the sector types and titles isn't relevant for that project, but I thought I mention it because it would be nice to have in FTL-profile-editor.

Niels-NTG commented 9 years ago

Now slightly less clunky and more usable:

log.info( getSectorInfo( gameState.getSectorNumber() ).getTitle() );

private SectorDot getSectorInfo( int index ) {
    ArrayList<SectorDot> visitedSectors = new ArrayList<SectorDot>();

    RandomSectorTreeGenerator myGen = new RandomSectorTreeGenerator( new NativeRandom() );
    List<List<SectorDot>> myColumns = myGen.generateSectorTree(
        gameState.getSectorTreeSeed(),
        gameStateArray.isDLCEnabled()
    );

    int columnsOffset = 0;
    for (int i = 0; i < myColumns.size(); i++) {
        for (int k = 0; k < myColumns.get(i).size(); k++) {
            if (gameState.getSectorVisitation().subList(
                    columnsOffset, columnsOffset + myColumns.get(i).size()
                ).get(k)
            ) {
                visitedSectors.add( myColumns.get(i).get(k) );
            }
        }
        columnsOffset += myColumns.get(i).size();
    }

    return visitedSectors.get( index );
}
Vhati commented 9 years ago

SectorDot .isVisited() always returns false

Oops. Yeah, sorry about that. That step applying breadcrumbs to the [columns statically or a SectorTree instance ] is what sets their visited status. I should add a code comment about that...

Niels-NTG commented 9 years ago

Again, thanks for the help. I now have a basic implementation running in my FTLAV project. Up next: make everything look less ugly and more like the look of the game itself.

Vhati commented 9 years ago

Sweet!

I'm curious, did you run into any problems reading continue.sav while FTL's running?

I've only done so at the main menu (totally safe). I assumed if I tried while playing, there's a risk of opening the file while FTL is partially overwriting it.

Niels-NTG commented 9 years ago

It might be dangerous to write something to continue.sav while FTL is running. But since I don't do that I haven't noticed any problems. I was pleasantly surprised when I figured out that unlike what the FTL Wiki says, continue.sav is re-generated each time the ship is at a "save" beacon (= there are no or no more enemies or events to deal with). With the knowledge I programmed a timer that checkes if the last modified time of the savefile has changed since compared to previous check. If this is true AND if the current total number of explored beacons is greater than last time, the graph gets updated.

Currently the application gives an error message when the savefile gets deleted during runtime (when you have a game-over). But that is pretty much the only file-related problem I have encountered since I got the aforementioned part working.

I might encounter more filesystem related problems when I get around to work on a system that writes the entire array of game states to a separate file. I will need this because currently all graphed data disappears after you quit and restart FTLAV.