Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fancy Save Picker + Autosave Feature #672

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions rsrc/dialogs/confirm-overwrite.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!-- NOTE: This file should be updated to use relative positioning the next time it changes. -->
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='cancel'>
<pict type='dlog' num='23' top='9' left='9'/>
<text name='prompt' top='5' left='51' width='255' height='32'>Are you sure you want to overwrite {File}?</text>
<button name='save' type='regular' top='46' left='178'>Save</button>
<button name='cancel' type='regular' def-key='esc' top='46' left='248'>Cancel</button>
</dialog>
69 changes: 69 additions & 0 deletions rsrc/dialogs/pick-save.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!-- NOTE: This file should be updated to use relative positioning the next time it changes. -->
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='cancel'>
<text name='title-load' size='large' top='9' left='130' width='126' height='17'>Load a party:</text>
<text name='title-auto' relative='pos-in pos-in' rel-anchor='prev' size='large' top='0' left='0' width='126' height='17'>Load an autosave from {Folder}:</text>
<text name='title-save' relative='pos-in pos-in' rel-anchor='prev' size='large' top='0' left='0' width='126' height='17'>Save your party:</text>
<stack name='list'>
<page default='true' template='files'>
<field name='file1-field' top='34' left='21' width='100' height='12'>NewParty</field>
<text name='file1-extension-label' relative='pos neg' rel-anchor='prev' top='1' left='3' width='100'>.exg</text>
<text name='file1' framed='true' top='34' left='21' width='400' height='80'>{File}</text>
<pict name='pc1a' relative='pos-in pos-in' rel-anchor='prev' type='pc' num='0' top='15' left='9'/>
<pict name='pc1b' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc1c' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc1d' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc1e' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc1f' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<text name='info1' relative='pos-in pos-in' anchor='file1' framed='true' top='4' left='249' width='138' height='71'>Avg. Level: {Lv}<br/>{LastSaved}<br/><br/>{Scenario}<br/><br/>{Location}</text>
<button name='save1' relative='pos-in pos-in' anchor='file1' type='regular' top='55' left='9'>Save</button>
<button name='load1' relative='pos-in pos-in' rel-anchor='prev' type='regular' top='0' left='0'>Load</button>
<button name='auto1' relative='pos-in pos-in' rel-anchor='prev' type='large' top='0' left='70'>Autosaves</button>
<text name='auto1-more-recent' relative='pos pos-in' rel-anchor='prev' top='6' left='3'>&lt;- Newer!</text>

<text name='file2' relative='pos-in pos-in' anchor='file1' framed='true' top='90' left='0' width='400' height='80'>{File}</text>
<pict name='pc2a' relative='pos-in pos-in' rel-anchor='prev' type='pc' num='0' top='15' left='9'/>
<pict name='pc2b' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc2c' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc2d' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc2e' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc2f' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<text name='info2' relative='pos-in pos-in' anchor='file2' framed='true' top='4' left='249' width='138' height='71'>Avg. Level: {Lv}<br/>{LastSaved}<br/><br/>{Scenario}<br/><br/>{Location}</text>
<button name='save2' relative='pos-in pos-in' anchor='file2' type='regular' top='55' left='9'>Save</button>
<button name='load2' relative='pos-in pos-in' rel-anchor='prev' type='regular' top='0' left='0'>Load</button>
<button name='auto2' relative='pos-in pos-in' rel-anchor='prev' type='large' top='0' left='70'>Autosaves</button>
<text name='auto2-more-recent' relative='pos pos-in' rel-anchor='prev' top='6' left='3'>&lt;- Newer!</text>

<text name='file3' relative='pos-in pos-in' anchor='file2' framed='true' top='90' left='0' width='400' height='80'>{File}</text>
<pict name='pc3a' relative='pos-in pos-in' rel-anchor='prev' type='pc' num='0' top='15' left='9'/>
<pict name='pc3b' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc3c' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc3d' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc3e' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc3f' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<text name='info3' relative='pos-in pos-in' anchor='file3' framed='true' top='4' left='249' width='138' height='71'>Avg. Level: {Lv}<br/>{LastSaved}<br/><br/>{Scenario}<br/><br/>{Location}</text>
<button name='save3' relative='pos-in pos-in' anchor='file3' type='regular' top='55' left='9'>Save</button>
<button name='load3' relative='pos-in pos-in' rel-anchor='prev' type='regular' top='0' left='0'>Load</button>
<button name='auto3' relative='pos-in pos-in' rel-anchor='prev' type='large' top='0' left='70'>Autosaves</button>
<text name='auto3-more-recent' relative='pos pos-in' rel-anchor='prev' top='6' left='3'>&lt;- Newer!</text>

<text name='file4' relative='pos-in pos-in' anchor='file3' framed='true' top='90' left='0' width='400' height='80'>{File}</text>
<pict name='pc4a' relative='pos-in pos-in' rel-anchor='prev' type='pc' num='0' top='15' left='9'/>
<pict name='pc4b' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc4c' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc4d' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc4e' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<pict name='pc4f' relative='pos-in' rel-anchor='prev' type='pc' num='0' top='0' left='40'/>
<text name='info4' relative='pos-in pos-in' anchor='file4' framed='true' top='4' left='249' width='138' height='71'>Avg. Level: {Lv}<br/>{LastSaved}<br/><br/>{Scenario}<br/><br/>{Location}</text>
<button name='save4' relative='pos-in pos-in' anchor='file4' type='regular' top='55' left='9'>Save</button>
<button name='load4' relative='pos-in pos-in' rel-anchor='prev' type='regular' top='0' left='0'>Load</button>
<button name='auto4' relative='pos-in pos-in' rel-anchor='prev' type='large' top='0' left='70'>Autosaves</button>
<text name='auto4-more-recent' relative='pos pos-in' rel-anchor='prev' top='6' left='3'>&lt;- Newer!</text>
</page>
</stack>
<button name='prev' relative='pos-in pos-in' anchor='file4' type='left' def-key='left' top='100' left='10'/>
<button name='next' relative='pos pos-in' rel-anchor='prev' type='right' def-key='right' top='0' left='0'/>
<button name='find' relative='pos-in pos-in' rel-anchor='prev' type='large' def-key='f' top='0' left='80'>File Browser</button>
<button name='cancel' relative='pos pos-in' rel-anchor='prev' type='regular' def-key='esc' top='0' left='20'>Cancel</button>
</dialog>
20 changes: 20 additions & 0 deletions rsrc/dialogs/pref-autosave.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='okay'>
<pict type='dlog' num='16' top='8' left='8'/>
<text size='large' relative='pos pos-in' rel-anchor='prev' top='0' left='4' width='218' height='16'>
Autosave Preferences
</text>
<text name='max-files-head' size='large' relative='pos-in pos' rel-anchor='prev' top='17' left='0'>Max autosave files:</text>
<field name='max-files' type='uint' relative='neg-in neg' rel-anchor='prev' top='2' left='5' width='40' height='15'/>
<text name='triggers-head' size='large' relative='neg pos' anchor='max-files-head' top='17' left='0' width='260' height='17'>Autosave when:</text>
<led name='RestComplete' relative='pos-in pos' anchor='triggers-head' top='4' left='4'>The party finishes resting</led>
<led name='TownWaitComplete' relative='pos-in pos' rel-anchor='prev' top='6' left='0'>The party spends a long time waiting in town/a dungeon</led>
<led name='Eat' relative='pos-in pos' rel-anchor='prev' top='6' left='0'>The party eats a full meal while wandering</led>
<led name='EnterTown' relative='pos-in pos' rel-anchor='prev' top='6' left='0'>The party enters town/a dungeon</led>
<led name='ExitTown' relative='pos-in pos' rel-anchor='prev' top='6' left='0'>The party exits town/a dungeon</led>
<led name='EndOutdoorCombat' relative='pos-in pos' rel-anchor='prev' top='6' left='0'>The party finishes combat outdoors</led>

<button name='okay' relative='abs pos' rel-anchor='prev' type='regular' top='7' left='354'>OK</button>
<button name='cancel' relative='neg pos-in' anchor='okay' type='regular' def-key='esc' top='0' left='73'>Cancel</button>
</dialog>
7 changes: 6 additions & 1 deletion rsrc/dialogs/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@
<text name='keyshift-note' relative='pos-in pos' anchor='keyshift-head' top='20' left='15' width='300' height='17'>(Holding Shift while using directional keys will do the opposite.)</text>
<text name='misc-head' size='large' relative='neg pos' anchor='keyshift-note' top='15' left='15' width='182' height='17'>Miscellaneous:</text>
<led name='nosound' relative='pos-in pos' anchor='misc-head' top='6' left='15'>No Sounds</led>
<led name='repeatdesc' relative='pos-in pos' rel-anchor='prev' top='10' left='0'>Show room descriptions more than once</led>
<led name='fancypicker' relative='pos-in pos' rel-anchor='prev' top='10' left='0'>Use in-game save file browser</led>
<led name='autosave-toggle' relative='pos-in pos' rel-anchor='prev' top='10' left='0'>Autosave</led>
<button name='autosave-details' type='tiny' text-size='10' relative='pos pos-in' rel-anchor='prev' top='0' left='4'>
Details
</button>
<led name='repeatdesc' relative='pos-in pos' anchor='autosave-toggle' top='10' left='0'>Show room descriptions more than once</led>
<led name='easier' relative='pos-in pos' rel-anchor='prev' top='10' left='0'>Make game easier (monsters much weaker)</led>
<led name='lesswm' relative='pos-in pos' rel-anchor='prev' top='10' left='0'>Fewer wandering monsters</led>
<led name='skipsplash' relative='pos-in pos' rel-anchor='prev' top='10' left='0'>Skip splash screen on startup</led>
Expand Down
2 changes: 1 addition & 1 deletion rsrc/schemas/dialog.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@
<xs:field xpath="@link"/>
</xs:keyref>
<xs:keyref name="anchorLink" refer="uniqueID">
<xs:selector xpath="*"/>
<xs:selector xpath="."/>
<xs:field xpath="@anchor"/>
</xs:keyref>
<xs:keyref name="keyRef" refer="uniqueID">
Expand Down
1 change: 1 addition & 0 deletions src/dialogxml/widgets/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ void cControl::playClickSound(){
}

bool cControl::handleClick(location, cFramerateLimiter& fps_limiter){
if(!visible) return false;
sf::Event e;
bool done = false, clicked = false;
getWindow().setActive();
Expand Down
12 changes: 12 additions & 0 deletions src/dialogxml/widgets/stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "message.hpp"
#include "pict.hpp"
#include "scrollbar.hpp"
#include "mathutil.hpp"
#include <climits>

bool cStack::hasChild(std::string id) const {
Expand Down Expand Up @@ -84,6 +85,17 @@ bool cStack::setPage(size_t n) {
return !failed;
}

void cStack::changeSelectedPage(int dir, bool loop) {
curPage += dir;
if(loop){
if(curPage < 0) curPage += nPages;
else if(curPage >= nPages) curPage -= nPages;
}else{
curPage = minmax(0, nPages - 1, curPage);
}
setPage(curPage);
}

size_t cStack::getPage() const {
return curPage;
}
Expand Down
4 changes: 4 additions & 0 deletions src/dialogxml/widgets/stack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class cStack : public cContainer {
/// @param The new page number
/// @return false if the page could not be changed, usually due to a focus handler
bool setPage(size_t n);
/// Page forward or backward in the stack
/// @param dir Usually -1 or 1
/// @param loop Beyond the first and last page, loop to the other side
void changeSelectedPage(int dir, bool loop = true);
/// Get the current page the stack is displaying.
/// @return The current page number
size_t getPage() const;
Expand Down
5 changes: 4 additions & 1 deletion src/fileio/fileio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ bool mac_is_intel(){
}
return _mac_is_intel;
}
fs::path progDir, tempDir, scenDir, replayDir;
fs::path progDir, tempDir, scenDir, replayDir, saveDir;

// This is here to avoid unnecessarily duplicating it in platform-specific files.
cursor_type Cursor::current = sword_curs;
Expand Down Expand Up @@ -81,6 +81,9 @@ void init_directories(const char* exec_path) {
replayDir = tempDir/"Replays";
fs::create_directories(replayDir);

saveDir = tempDir/"Saves";
fs::create_directories(saveDir);

add_resmgr_paths(tempDir/"data");
tempDir /= "Temporary Files";

Expand Down
13 changes: 12 additions & 1 deletion src/fileio/fileio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#ifndef BOE_FILEIO_HPP
#define BOE_FILEIO_HPP

#include <ctime>
#include <string>
#include <vector>
#include <set>
#include <sstream>
#include <SFML/System/InputStream.hpp>
#include <boost/filesystem/path.hpp>
Expand All @@ -27,8 +29,17 @@ bool load_scenario(fs::path file_to_load, cScenario& scenario, bool only_header
fs::path nav_get_or_decode_party();
fs::path nav_put_or_temp_party(fs::path def = "");

bool load_party(fs::path file_to_load, cUniverse& univ);
fs::path os_file_picker(bool saving);
// The game implements a fancy file picker, the editors just call the OS picker.
extern fs::path run_file_picker(bool saving);

const std::set<fs::path> save_extensions = {".exg", ".boe", ".SAV", ".mac"};
// Return a directory's files sorted by last modified time
std::vector<std::pair<fs::path, std::time_t>> sorted_file_mtimes(fs::path dir, std::set<fs::path> valid_extensions = save_extensions);

bool load_party(fs::path file_to_load, cUniverse& univ, bool record = true);
bool save_party(cUniverse& univ, bool save_as = false);
bool save_party_force(cUniverse& univ, fs::path file);

void init_directories(const char* exec_path);

Expand Down
45 changes: 39 additions & 6 deletions src/fileio/fileio_party.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "fileio/tagfile.hpp"
#include "fileio/tarball.hpp"
#include "replay.hpp"
#include "game/boe.dlgutil.hpp"

extern bool mac_is_intel();
extern fs::path progDir, tempDir;
Expand All @@ -39,6 +40,8 @@ fs::path nav_get_or_decode_party() {
decode_file(next_action.GetText(), tempDir / "temp.exg");
return tempDir / "temp.exg";
}else{
// TODO if the save is not in the saves folder, prompt about moving it in,
// and return the moved path?
return nav_get_party();
}
}
Expand All @@ -51,7 +54,14 @@ fs::path nav_put_or_temp_party(fs::path def) {
}
}

bool load_party(fs::path file_to_load, cUniverse& univ){
fs::path os_file_picker(bool saving) {
if(saving)
return nav_put_or_temp_party();
else
return nav_get_or_decode_party();
}

bool load_party(fs::path file_to_load, cUniverse& univ, bool record){
bool town_restore = false;
bool maps_there = false;
bool in_scen = false;
Expand Down Expand Up @@ -157,7 +167,7 @@ bool load_party(fs::path file_to_load, cUniverse& univ){
break;
}

if(recording && result){
if(recording && record && result){
record_action("load_party", encode_file(file_to_load), true);
}

Expand Down Expand Up @@ -304,7 +314,6 @@ bool load_party_v1(fs::path file_to_load, cUniverse& real_univ, bool town_restor
return true;
}

extern fs::path scenDir;
bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){
igzstream zin(file_to_load.string().c_str());
tarball partyIn;
Expand Down Expand Up @@ -442,9 +451,12 @@ bool load_party_v2(fs::path file_to_load, cUniverse& real_univ){
return true;
}

static bool save_party_const(const cUniverse& univ, bool save_as) {
static bool save_party_const(const cUniverse& univ, bool save_as, fs::path dest_file = "") {
// Make sure it has the proper file extension
fs::path dest_file = univ.file;
if(dest_file.empty()){
dest_file = univ.file;
}

if(dest_file.extension() != ".exg"){
dest_file += ".exg";
}
Expand Down Expand Up @@ -546,9 +558,30 @@ bool save_party(cUniverse& univ, bool save_as) {
// univ.file can be empty for prefab parties, so a file browser might be needed
// even for a regular save.
if(save_as || univ.file.empty()){
univ.file = nav_put_or_temp_party();
univ.file = run_file_picker(true);
}
// A file wasn't chosen
if(univ.file.empty()) return false;
return save_party_const(univ, save_as);
}

bool save_party_force(cUniverse& univ, fs::path file) {
return save_party_const(univ, false, file);
}

static bool compare_mtime(std::pair<fs::path, std::time_t> a, std::pair<fs::path, std::time_t> b) {
return std::difftime(a.second, b.second) > 0;
}

std::vector<std::pair<fs::path, std::time_t>> sorted_file_mtimes(fs::path dir, std::set<fs::path> valid_extensions){
std::vector<std::pair<fs::path, std::time_t>> files;
for(fs::directory_iterator it{dir}; it != fs::directory_iterator{}; ++it) {
fs::path file = *it;
if(valid_extensions.count(file.extension())){
files.push_back(std::make_pair(file, last_write_time(file)));
}
}

std::sort(files.begin(), files.end(), compare_mtime);
return files;
}
16 changes: 11 additions & 5 deletions src/game/boe.actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ void handle_rest(bool& need_redraw, bool& need_reprint) {
pause(25);
univ.party.food -= 6;
while(i < 50) {
increase_age();
increase_age(false);
if(get_ran(1,1,2) == 2)
do_monsters();
if(get_ran(1,1,70) == 10)
Expand All @@ -485,6 +485,7 @@ void handle_rest(bool& need_redraw, bool& need_reprint) {
if(i == 50) {
do_rest(1200, get_ran(5,1,10), 50);
add_string_to_buf(" Rest successful.");
try_auto_save("RestComplete");
put_pc_screen();
pause(25);
}
Expand Down Expand Up @@ -1112,7 +1113,7 @@ static void handle_town_wait(bool& need_redraw, bool& need_reprint) {
}

for(int i = 0; i < 80 && !party_sees_a_monst(); i++){
increase_age();
increase_age(false);
do_monsters();
do_monster_turn();
int make_wand = get_ran(1,1,160 - univ.town->difficulty);
Expand All @@ -1132,6 +1133,9 @@ static void handle_town_wait(bool& need_redraw, bool& need_reprint) {
redraw_screen(REFRESH_NONE);
}
put_pc_screen();
if(!party_sees_a_monst()){
try_auto_save("TownWaitComplete");
}
}

void handle_wait(bool& did_something, bool& need_redraw, bool& need_reprint) {
Expand Down Expand Up @@ -2900,14 +2904,15 @@ bool handle_scroll(const sf::Event& event) {
}

void do_load() {
// TODO this needs to be changed/moved because a picker dialog opens now!!!
// Edge case: Replay can be cut off before a file is chosen,
// or party selection can be canceled, and this will cause
// a crash trying to decode a party
if(replaying && !has_next_action("load_party")){
return;
}

fs::path file_to_load = nav_get_or_decode_party();
fs::path file_to_load = run_file_picker(false);
if(file_to_load.empty()) return;
if(!load_party(file_to_load, univ))
return;
Expand Down Expand Up @@ -3060,7 +3065,7 @@ void do_rest(long length, int hp_restore, int mp_restore) {
adjust_spell_menus();
}

void increase_age() {
void increase_age(bool eating_trigger_autosave) {
short how_many_short = 0,r1;


Expand Down Expand Up @@ -3188,6 +3193,7 @@ void increase_age() {
else {
play_sound(6);
add_string_to_buf("You eat.");
if(eating_trigger_autosave) try_auto_save("Eat");
}
}

Expand Down Expand Up @@ -3421,7 +3427,7 @@ void handle_death() {
return;
}
else if(choice == "load") {
fs::path file_to_load = nav_get_or_decode_party();
fs::path file_to_load = run_file_picker(false);
if(!file_to_load.empty()){
if(load_party(file_to_load, univ)){
finish_load_party();
Expand Down
Loading
Loading