Skip to content

Commit c9ba65b

Browse files
committed
Implement character archive feature
Also adds support for multiple savegame location and lists custom quest characters.
1 parent c4fd7dd commit c9ba65b

20 files changed

+433
-83
lines changed

README.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,32 @@ This software requires **Microsoft Windows (64-bit recommended)**. You don't nee
2626

2727
Keep in mind that this software make modifications to your save game (more specifically the file Player.chr). You shouldn't modify your characters while the game is running, you can corrupt your save game. ***YOU SHOULD ALWAYS KEEP A FULL BACKUP OF YOUR SAVE GAME FOLDER*** (Documents/My Games/Titan Quest - Immortal Throne).
2828

29-
![TQRespec Screenshot 1](assets/screenshot_attributes.png "Attributes")
30-
![TQRespec Screenshot 2](assets/screenshot_skills.png "Skills")
31-
![TQRespec Screenshot 3](assets/screenshot_misc.png "Misc")
32-
![TQRespec Screenshot 4](assets/screenshot_characters.png "Characters")
29+
#### ***Screenshots***
30+
31+
- *Attributes*
32+
33+
![TQRespec Screenshot 1](assets/screenshot_attributes.png "Attributes")
34+
35+
36+
- *Remove Skills and masteries*
37+
38+
![TQRespec Screenshot 2](assets/screenshot_skills.png "Skills")
39+
40+
41+
- *Copy, Backup, Convert and JSON export*
42+
43+
![TQRespec Screenshot 3](assets/screenshot_misc.png "Misc")
44+
45+
46+
- *Characters list and export CSV*
47+
48+
![TQRespec Screenshot 4](assets/screenshot_characters.png "Characters")
49+
50+
51+
- *Characters Archive and Unarchive **(archive to hide character in game)***
52+
53+
![TQRespec Screenshot 5](assets/screenshot_archive.png "Archive")
54+
3355

3456
#### ***Running without the game installed***
3557

@@ -74,7 +96,7 @@ If you have a mastery on level 24 and 7 skills with points allocated, you can cl
7496

7597
#### ***Characters list and stats***
7698

77-
When you click on the button near the character selection list, you will be able to see a table with all your characters, with attributes and stats. There's also an option to export the table to CSV.
99+
When you click on the button near the character selection list, you will be able to see a table with all your characters, with attributes and stats. There's also an option to export the table to CSV. To Archive or Unarchive a character, right click on the row and select the option desired. When the archive is done, the character directory is moved from "SaveData/Main/_charactername" to "SaveData/Main/ArchivedCharacters/_charactername", hiding it from the game. Unarchive moves the character back to default directory.
78100

79101
#### ***Android support***
80102

assets/screenshot_archive.png

220 KB
Loading

src/main/java/br/com/pinter/tqrespec/gui/CharactersViewController.java

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import br.com.pinter.tqrespec.core.UnhandledRuntimeException;
2525
import br.com.pinter.tqrespec.core.WorkerThread;
2626
import br.com.pinter.tqrespec.logging.Log;
27+
import br.com.pinter.tqrespec.save.SaveLocation;
28+
import br.com.pinter.tqrespec.save.player.Archiver;
2729
import br.com.pinter.tqrespec.save.player.PlayerLoader;
2830
import br.com.pinter.tqrespec.tqdata.*;
2931
import br.com.pinter.tqrespec.util.Build;
@@ -36,6 +38,9 @@
3638
import javafx.scene.Cursor;
3739
import javafx.scene.Scene;
3840
import javafx.scene.control.*;
41+
import javafx.scene.control.Button;
42+
import javafx.scene.control.Label;
43+
import javafx.scene.control.MenuItem;
3944
import javafx.scene.control.cell.PropertyValueFactory;
4045
import javafx.scene.input.KeyCode;
4146
import javafx.scene.input.KeyEvent;
@@ -45,6 +50,7 @@
4550
import javafx.scene.paint.Color;
4651
import javafx.scene.text.Text;
4752
import javafx.stage.*;
53+
import javafx.stage.Window;
4854
import javafx.util.Callback;
4955
import org.apache.commons.lang3.SystemUtils;
5056

@@ -91,6 +97,9 @@ public class CharactersViewController implements Initializable {
9197
@FXML
9298
private TableColumn<PlayerCharacter, String> colName;
9399

100+
@FXML
101+
private TableColumn<PlayerCharacter, String> colStore;
102+
94103
@FXML
95104
private TableColumn<PlayerCharacter, Integer> colLevel;
96105

@@ -176,6 +185,9 @@ public class CharactersViewController implements Initializable {
176185

177186
private AtomicBoolean loadingCharacters = new AtomicBoolean(false);
178187

188+
@Inject
189+
private Archiver archiver;
190+
179191
@FXML
180192
public void closeWindow(@SuppressWarnings("unused") MouseEvent evt) {
181193
close();
@@ -225,6 +237,34 @@ public void moveWindow(MouseEvent evt) {
225237
}
226238
}
227239

240+
private void loadCharacters() {
241+
loadingCharacters.set(true);
242+
243+
Platform.runLater(() -> {
244+
charactersTable.setPlaceholder(new Label(ResourceHelper.getMessage("characters.loadingPlaceholder")));
245+
rootElement.getScene().setCursor(Cursor.WAIT);
246+
247+
});
248+
249+
characters = new ArrayList<>();
250+
for (PlayerCharacterFile p : gameInfo.getPlayerCharacterList()) {
251+
try {
252+
player.loadPlayer(p.getPlayerName(), p.getLocation());
253+
} catch (RuntimeException e) {
254+
logger.log(System.Logger.Level.ERROR, String.format("Error loading character '%s'", p));
255+
continue;
256+
}
257+
characters.add(player.getCharacter());
258+
}
259+
260+
Platform.runLater(() -> {
261+
setupTable();
262+
charactersTable.setPlaceholder(new Label(""));
263+
Platform.runLater(() -> rootElement.getScene().setCursor(Cursor.DEFAULT));
264+
loadingCharacters.set(false);
265+
});
266+
}
267+
228268
@Override
229269
public void initialize(URL url, ResourceBundle resourceBundle) {
230270
player.reset();
@@ -269,31 +309,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) {
269309
stage.addEventHandler(WindowEvent.WINDOW_SHOWING, e -> new WorkerThread(new MyTask<>() {
270310
@Override
271311
protected Void call() {
272-
loadingCharacters.set(true);
273-
274-
Platform.runLater(() -> {
275-
charactersTable.setPlaceholder(new Label(ResourceHelper.getMessage("characters.loadingPlaceholder")));
276-
rootElement.getScene().setCursor(Cursor.WAIT);
277-
278-
});
279-
280-
characters = new ArrayList<>();
281-
for (PlayerCharacterFile p : gameInfo.getPlayerCharacterList()) {
282-
try {
283-
player.loadPlayer(p.getPlayerName(), p.isExternal());
284-
} catch (RuntimeException e) {
285-
logger.log(System.Logger.Level.ERROR, String.format("Error loading character '%s'", p));
286-
continue;
287-
}
288-
characters.add(player.getCharacter());
289-
}
290-
291-
Platform.runLater(() -> {
292-
setupTable();
293-
charactersTable.setPlaceholder(new Label(""));
294-
Platform.runLater(() -> rootElement.getScene().setCursor(Cursor.DEFAULT));
295-
loadingCharacters.set(false);
296-
});
312+
loadCharacters();
297313
return null;
298314
}
299315
}).start());
@@ -305,14 +321,84 @@ protected Void call() {
305321

306322
exportButton.setGraphic(Icon.FA_FILE_EXPORT.create());
307323

324+
ContextMenu contextMenu = new ContextMenu();
325+
MenuItem archive = new MenuItem(ResourceHelper.getMessage("characters.archive"));
326+
MenuItem unarchive = new MenuItem(ResourceHelper.getMessage("characters.unarchive"));
327+
MenuItem explore = new MenuItem(ResourceHelper.getMessage("characters.explore"));
328+
contextMenu.getItems().add(explore);
329+
contextMenu.getItems().add(new SeparatorMenuItem());
330+
contextMenu.getItems().add(archive);
331+
contextMenu.getItems().add(unarchive);
332+
333+
explore.setOnAction(event -> {
334+
try {
335+
Runtime.getRuntime().exec(Constants.EXPLORER_COMMAND + " "
336+
+ charactersTable.getSelectionModel().getSelectedItem().getPath().toString());
337+
} catch (IOException e) {
338+
logger.log(System.Logger.Level.WARNING, "unable to open explorer: ", e);
339+
}
340+
});
341+
archive.setOnAction(event -> archiveAction(false));
342+
unarchive.setOnAction(event -> archiveAction(true));
343+
344+
charactersTable.setContextMenu(contextMenu);
345+
contextMenu.setOnShowing(e -> {
346+
int selectedCount = charactersTable.getSelectionModel().getSelectedItems().size();
347+
PlayerCharacter selected = charactersTable.getSelectionModel().getSelectedItem();
348+
archive.setDisable(selectedCount > 1 || !selected.isArchivable());
349+
unarchive.setDisable(selectedCount > 1 || !selected.isArchived());
350+
});
308351
stage.show();
309352
}
310353

354+
private void archiveAction(boolean undo) {
355+
PlayerCharacter selected = charactersTable.getSelectionModel().getSelectedItem();
356+
357+
String toastHeader = null;
358+
String toastContent = null;
359+
360+
try {
361+
rootElement.getScene().setCursor(Cursor.WAIT);
362+
if(undo) {
363+
archiver.unarchive(selected);
364+
toastHeader = "characters.unarchive";
365+
toastContent = "characters.unarchivedmessage";
366+
} else {
367+
archiver.archive(selected);
368+
toastHeader = "characters.archive";
369+
toastContent = "characters.archivedmessage";
370+
}
371+
} catch (IOException e) {
372+
throw new UnhandledRuntimeException(e);
373+
} finally {
374+
reset();
375+
SaveLocation locationMessage = selected.getLocation();
376+
if(locationMessage.equals(SaveLocation.ARCHIVEMAIN)) {
377+
locationMessage = SaveLocation.MAIN;
378+
}
379+
if(locationMessage.equals(SaveLocation.ARCHIVEUSER)) {
380+
locationMessage = SaveLocation.USER;
381+
}
382+
383+
rootElement.getScene().setCursor(Cursor.DEFAULT);
384+
Toast.show((Stage) rootElement.getScene().getWindow(),
385+
ResourceHelper.getMessage(toastHeader),
386+
ResourceHelper.getMessage(toastContent,
387+
selected.getName(), ResourceHelper.getMessage("characters.store." + locationMessage)),
388+
5000);
389+
}
390+
}
391+
311392
private void setupTable() {
312393
//setup tableview
313394
setupTableColumnString(colName, ResourceHelper.getMessage("characters.characterName"), "name");
395+
setupTableColumnString(colStore, ResourceHelper.getMessage("characters.store"), null);
314396
setupTableColumnInteger(colLevel, ResourceHelper.getMessage("main.charlevel"), "level");
315397
setupTableColumnString(colGender, ResourceHelper.getMessage("main.gender"), null);
398+
399+
colStore.setCellValueFactory( f -> new SimpleStringProperty(
400+
ResourceHelper.getMessage("characters.store."+ f.getValue().getLocation().toString().toUpperCase())
401+
));
316402
colGender.setCellValueFactory(f -> new SimpleStringProperty(
317403
ResourceHelper.getMessage("main.gender." + f.getValue().getGender().name().toLowerCase())));
318404

@@ -399,6 +485,15 @@ private void setupTable() {
399485

400486
charactersTable.getItems().addAll(characters);
401487

488+
charactersTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
489+
490+
resizeCharactersTable();
491+
}
492+
493+
private void reset() {
494+
charactersTable.getItems().clear();
495+
characters.clear();
496+
loadCharacters();
402497
resizeCharactersTable();
403498
}
404499

@@ -448,6 +543,7 @@ public void exportCsv() {
448543

449544
String[] header = new String[]{
450545
ResourceHelper.getMessage("characters.characterName"),
546+
ResourceHelper.getMessage("characters.store"),
451547
ResourceHelper.getMessage("main.charlevel"),
452548
ResourceHelper.getMessage("main.gender"),
453549
ResourceHelper.getMessage("main.charclass"),
@@ -517,6 +613,7 @@ public void exportCsv() {
517613

518614
String[] row = new String[]{
519615
p.getName(),
616+
ResourceHelper.getMessage("characters.store." + p.getLocation()),
520617
String.valueOf(p.getLevel()),
521618
gender,
522619
p.getCharacterClass(),

src/main/java/br/com/pinter/tqrespec/gui/MainController.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import br.com.pinter.tqrespec.core.*;
2424
import br.com.pinter.tqrespec.logging.Log;
25+
import br.com.pinter.tqrespec.save.SaveLocation;
2526
import br.com.pinter.tqrespec.save.player.Player;
2627
import br.com.pinter.tqrespec.save.player.PlayerWriter;
2728
import br.com.pinter.tqrespec.tqdata.Db;
@@ -154,12 +155,20 @@ protected Void call() {
154155

155156
State.get().gameRunningProperty().addListener((value, oldV, newV) -> {
156157
if (BooleanUtils.isTrue(newV)) {
158+
Platform.runLater(() -> {
159+
Parent rootCharWindow = fxmlLoaderCharacter.getRoot();
160+
if(rootCharWindow != null) {
161+
Stage charactersWindow = (Stage) rootCharWindow.getScene().getWindow();
162+
charactersWindow.close();
163+
}
164+
});
165+
157166
Platform.runLater(() -> {
158167
reset();
159168
Toast.show((Stage) rootelement.getScene().getWindow(),
160169
ResourceHelper.getMessage("alert.errorgamerunning_header"),
161170
ResourceHelper.getMessage("alert.errorgamerunning_content"),
162-
8000);
171+
30000);
163172
});
164173
}
165174
});
@@ -170,7 +179,7 @@ protected Void call() {
170179
public void addCharactersToCombo() {
171180
try {
172181
characterCombo.getSelectionModel().clearSelection();
173-
characterCombo.getItems().setAll(gameInfo.getPlayerCharacterList());
182+
characterCombo.getItems().setAll(gameInfo.getPlayerCharacterList(SaveLocation.MAIN, SaveLocation.EXTERNAL));
174183
characterCombo.getItems().sort(Comparator.comparing(PlayerCharacterFile::getPlayerName));
175184
} catch (ClassCastException | UnsupportedOperationException | IllegalArgumentException e) {
176185
logger.log(System.Logger.Level.ERROR, Constants.ERROR_MSG_EXCEPTION, e);
@@ -228,9 +237,14 @@ public void openCharactersWindow(ActionEvent evt) throws IOException {
228237
fxmlLoaderCharacter.setLocation(ResourceHelper.getResourceUrl("/fxml/characters.fxml"));
229238
fxmlLoaderCharacter.setResources(ResourceBundle.getBundle("i18n.UI", State.get().getLocale()));
230239
fxmlLoaderCharacter.load();
240+
Parent r = fxmlLoaderCharacter.getRoot();
241+
Stage charactersWindow = (Stage) r.getScene().getWindow();
242+
charactersWindow.setOnHiding(e -> reset());
231243
} else {
232244
root = fxmlLoaderCharacter.getRoot();
233-
((Stage) root.getScene().getWindow()).show();
245+
Stage charactersWindow = (Stage) root.getScene().getWindow();
246+
charactersWindow.setOnHiding(e -> reset());
247+
charactersWindow.show();
234248
}
235249
}
236250

@@ -371,7 +385,7 @@ public void characterSelected(ActionEvent evt) {
371385
MyTask<Boolean> loadTask = new MyTask<>() {
372386
@Override
373387
protected Boolean call() {
374-
return player.loadPlayer(playerCharacterFile.getPlayerName(), playerCharacterFile.isExternal());
388+
return player.loadPlayer(playerCharacterFile.getPlayerName(), playerCharacterFile.getLocation());
375389
}
376390
};
377391

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (C) 2022 Emerson Pinter - All Rights Reserved
3+
*/
4+
5+
package br.com.pinter.tqrespec.save;
6+
7+
public enum SaveLocation {
8+
MAIN,
9+
USER,
10+
EXTERNAL,
11+
ARCHIVEMAIN,
12+
ARCHIVEUSER
13+
}

0 commit comments

Comments
 (0)