diff --git a/src/fileio.cpp b/src/fileio.cpp index 163a20dc86..6999e54868 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -43,6 +43,7 @@ static const char * const _subdirs[] = { "save" PATHSEP "autosave" PATHSEP, "scenario" PATHSEP, "scenario" PATHSEP "heightmap" PATHSEP, + "orderlist" PATHSEP, "gm" PATHSEP, "data" PATHSEP, "baseset" PATHSEP, @@ -1100,7 +1101,7 @@ void DeterminePaths(const char *exe, bool only_local_path) DEBUG(misc, 1, "%s found as personal directory", _personal_dir.c_str()); static const Subdirectory default_subdirs[] = { - SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR, SOCIAL_INTEGRATION_DIR + SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, ORDERLIST_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR, SOCIAL_INTEGRATION_DIR }; for (uint i = 0; i < lengthof(default_subdirs); i++) { diff --git a/src/fileio_type.h b/src/fileio_type.h index c871c1c14b..8c0a1ba475 100644 --- a/src/fileio_type.h +++ b/src/fileio_type.h @@ -18,6 +18,7 @@ enum AbstractFileType { FT_SAVEGAME, ///< old or new savegame FT_SCENARIO, ///< old or new scenario FT_HEIGHTMAP, ///< heightmap file + FT_ORDERLIST, ///< orderlist file FT_INVALID = 7, ///< Invalid or unknown file type. FT_NUMBITS = 3, ///< Number of bits required for storing a #AbstractFileType value. @@ -34,6 +35,9 @@ enum DetailedFileType { DFT_HEIGHTMAP_BMP, ///< BMP file. DFT_HEIGHTMAP_PNG, ///< PNG file. + /* Orderlist files */ + DFT_ORDERLIST, ///< JSON file. + /* fios 'files' */ DFT_FIOS_DRIVE, ///< A drive (letter) entry. DFT_FIOS_PARENT, ///< A parent directory entry. @@ -76,6 +80,7 @@ enum FiosType { FIOS_TYPE_OLD_SCENARIO = MAKE_FIOS_TYPE(FT_SCENARIO, DFT_OLD_GAME_FILE), FIOS_TYPE_PNG = MAKE_FIOS_TYPE(FT_HEIGHTMAP, DFT_HEIGHTMAP_PNG), FIOS_TYPE_BMP = MAKE_FIOS_TYPE(FT_HEIGHTMAP, DFT_HEIGHTMAP_BMP), + FIOS_TYPE_ORDERLIST = MAKE_FIOS_TYPE(FT_ORDERLIST, DFT_ORDERLIST), FIOS_TYPE_INVALID = MAKE_FIOS_TYPE(FT_INVALID, DFT_INVALID), }; @@ -111,6 +116,7 @@ enum Subdirectory { AUTOSAVE_DIR, ///< Subdirectory of save for autosaves SCENARIO_DIR, ///< Base directory for all scenarios HEIGHTMAP_DIR, ///< Subdirectory of scenario for heightmaps + ORDERLIST_DIR, ///< Subdirectort for all orderlists OLD_GM_DIR, ///< Old subdirectory for the music OLD_DATA_DIR, ///< Old subdirectory for the data. BASESET_DIR, ///< Subdirectory for all base data (base sets, intro game) diff --git a/src/fios.cpp b/src/fios.cpp index d34f6070a0..ec7c58b142 100644 --- a/src/fios.cpp +++ b/src/fios.cpp @@ -91,6 +91,10 @@ void FileList::BuildFileList(AbstractFileType abstract_filetype, SaveLoadOperati FiosGetHeightmapList(fop, show_dirs, *this); break; + case FT_ORDERLIST: + FiosGetOrderlistList(fop, show_dirs, *this); + break; + default: NOT_REACHED(); } @@ -185,6 +189,7 @@ bool FiosBrowseTo(const FiosItem *item) case FIOS_TYPE_OLDFILE: case FIOS_TYPE_SCENARIO: case FIOS_TYPE_OLD_SCENARIO: + case FIOS_TYPE_ORDERLIST: case FIOS_TYPE_PNG: case FIOS_TYPE_BMP: return false; @@ -224,6 +229,11 @@ static std::string FiosMakeFilename(const std::string *path, const char *name, c * @param last Last element of buffer \a buf. * @return The completed filename. */ + +std::string FiosMakeOrderListName(const char *name) +{ + return FiosMakeFilename(_fios_path, name, ".json"); +} std::string FiosMakeSavegameName(const char *name) { const char *extension = (_game_mode == GM_EDITOR) ? ".scn" : ".sav"; @@ -249,9 +259,23 @@ std::string FiosMakeHeightmapName(const char *name) * @param name Filename to delete. * @return Whether the file deletion was successful. */ -bool FiosDelete(const char *name) +bool FiosDelete(const char *name,AbstractFileType ft) { - std::string filename = FiosMakeSavegameName(name); + std::string filename; + + switch (ft) { + case FT_SAVEGAME: + case FT_SCENARIO: + filename = FiosMakeSavegameName(name); + break; + case FT_ORDERLIST: + filename = FiosMakeOrderListName(name); + break; + default: + NOT_REACHED(); + break; + } + return unlink(filename.c_str()) == 0; } @@ -499,6 +523,52 @@ void FiosGetSavegameList(SaveLoadOperation fop, bool show_dirs, FileList &file_l FiosGetFileList(fop, show_dirs, &FiosGetSavegameListCallback, NO_DIRECTORY, file_list); } +/** + * Callback for FiosGetOrderlistList. It tells if a file is a orederlist or not. + * @param fop Purpose of collecting the list. + * @param file Name of the file to check. + * @param ext A pointer to the extension identifier inside file + * @param title Buffer if a callback wants to lookup the title of the file; nullptr to skip the lookup + * @param last Last available byte in buffer (to prevent buffer overflows); not used when title == nullptr + * @return a FIOS_TYPE_* type of the found file, FIOS_TYPE_INVALID if not a savegame + * @see FiosGetFileList + * @see FiosGetOrderlistList + */ +FiosType FiosGetOrderlistListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last) +{ + /* Show orderlist files + * .json orderlist files + */ + + /* Don't crash if we supply no extension */ + if (ext == nullptr) ext = ""; + + if (StrEqualsIgnoreCase(ext, ".json")) { + GetFileTitle(file, title, last, ORDERLIST_DIR); + return FIOS_TYPE_ORDERLIST; + } + + return FIOS_TYPE_INVALID; +} + +/** + * Get a list of orderlists. + * @param fop Purpose of collecting the list. + * @param show_dirs Whether to show directories. + * @param file_list Destination of the found files. + * @see FiosGetFileList + */ +void FiosGetOrderlistList(SaveLoadOperation fop, bool show_dirs, FileList &file_list) +{ + static std::optional fios_save_path; + + if (!fios_save_path) fios_save_path = FioFindDirectory(ORDERLIST_DIR); + + _fios_path = &(*fios_save_path); + + FiosGetFileList(fop, show_dirs, &FiosGetOrderlistListCallback, NO_DIRECTORY, file_list); +} + /** * Callback for FiosGetFileList. It tells if a file is a scenario or not. * @param fop Purpose of collecting the list. diff --git a/src/fios.h b/src/fios.h index 026593b787..6a092b7093 100644 --- a/src/fios.h +++ b/src/fios.h @@ -15,6 +15,7 @@ #include "newgrf_config.h" #include "network/core/tcp_content_type.h" #include +#include /** Special values for save-load window for the data parameter of #InvalidateWindowData. */ @@ -51,23 +52,27 @@ DECLARE_ENUM_AS_BIT_SET(SortingBits) /* Variables to display file lists */ extern SortingBits _savegame_sort_order; -void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop); +void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop,const Vehicle * veh = nullptr); void FiosGetSavegameList(SaveLoadOperation fop, bool show_dirs, FileList &file_list); void FiosGetScenarioList(SaveLoadOperation fop, bool show_dirs, FileList &file_list); void FiosGetHeightmapList(SaveLoadOperation fop, bool show_dirs, FileList &file_list); +void FiosGetOrderlistList(SaveLoadOperation fop, bool show_dirs, FileList &file_list); bool FiosBrowseTo(const FiosItem *item); std::string FiosGetCurrentPath(); std::optional FiosGetDiskFreeSpace(const std::string &path); -bool FiosDelete(const char *name); +bool FiosDelete(const char *name, AbstractFileType file_type); std::string FiosMakeHeightmapName(const char *name); std::string FiosMakeSavegameName(const char *name); +std::string FiosMakeOrderListName(const char *name); + FiosType FiosGetSavegameListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last); FiosType FiosGetScenarioListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last); FiosType FiosGetHeightmapListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last); +FiosType FiosGetOrderlistListCallback(SaveLoadOperation fop, const std::string &file, const char *ext, char *title, const char *last); void ScanScenarios(); const char *FindScenario(const ContentInfo *ci, bool md5sum); diff --git a/src/fios_gui.cpp b/src/fios_gui.cpp index e4b4318372..9454952c80 100644 --- a/src/fios_gui.cpp +++ b/src/fios_gui.cpp @@ -29,6 +29,11 @@ #include "gamelog.h" #include "stringfilter_type.h" #include "gamelog.h" +#include "vehicle_base.h" +#include +#include +#include +#include #include "widgets/fios_widget.h" @@ -234,6 +239,101 @@ static constexpr NWidgetPart _nested_save_dialog_widgets[] = { EndContainer(), }; +/** Save Orderlist */ +static constexpr NWidgetPart _nested_save_orderlist_dialog_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + EndContainer(), + /* Current directory and free space */ + NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + /* Left side : filter box and available files */ + NWidget(NWID_VERTICAL), + /* Filter box with label */ + NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1), + NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect.top, 0, WidgetDimensions::unscaled.framerect.bottom, 0), + SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.frametext.right, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL), + NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0), + SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), + EndContainer(), + EndContainer(), + /* Sort buttons */ + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0), + EndContainer(), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON), + EndContainer(), + /* Files */ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND), + NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetPadding(2, 2, 2, 2), + SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), + NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_SAVE_OSK_TITLE), SetPadding(2, 2, 2, 2), SetFill(1, 0), SetResize(1, 0), + SetDataTip(STR_SAVELOAD_OSKTITLE, STR_SAVELOAD_EDITBOX_TOOLTIP), + EndContainer(), + + /* Save button*/ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_DELETE_SELECTION), SetDataTip(STR_SAVELOAD_DELETE_BUTTON, STR_SAVELOAD_DELETE_TOOLTIP), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SAVE_GAME), SetDataTip(STR_SAVELOAD_SAVE_BUTTON, STR_SAVELOAD_SAVE_TOOLTIP), SetFill(1, 0), SetResize(1, 0), + EndContainer(), + + EndContainer(), + EndContainer(), +}; + +/** Load Orderlist */ +static constexpr NWidgetPart _nested_load_orderlist_dialog_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + EndContainer(), + /* Current directory and free space */ + NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(), + + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + /* Left side : filter box and available files */ + NWidget(NWID_VERTICAL), + /* Filter box with label */ + NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1), + NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect.top, 0, WidgetDimensions::unscaled.framerect.bottom, 0), + SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.frametext.right, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL), + NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetMinimalSize(50, 12), SetResize(1, 0), + SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), + EndContainer(), + EndContainer(), + /* Sort buttons */ + NWidget(NWID_HORIZONTAL), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0), + EndContainer(), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetMinimalSize(12, 12), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON), + EndContainer(), + /* Files */ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND), + NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 2, 2, 2), + SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR), + EndContainer(), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_TOOLTIP), SetFill(1, 0), SetResize(1, 0), + EndContainer(), + EndContainer(), +}; + /** Text colours of #DetailedFileType fios entries in the window. */ static const TextColour _fios_colours[] = { TC_LIGHT_BROWN, // DFT_OLD_GAME_FILE @@ -278,6 +378,7 @@ struct SaveLoadWindow : public Window { private: static const uint EDITBOX_MAX_SIZE = 50; + const Vehicle* veh; QueryString filename_editbox; ///< Filename editbox. AbstractFileType abstract_filetype; /// Type of file to select. SaveLoadOperation fop; ///< File operation to perform. @@ -311,11 +412,13 @@ public: this->filename_editbox.text.Assign(GenerateDefaultSaveName()); } - SaveLoadWindow(WindowDesc *desc, AbstractFileType abstract_filetype, SaveLoadOperation fop) + SaveLoadWindow(WindowDesc *desc, AbstractFileType abstract_filetype, SaveLoadOperation fop,const Vehicle * veh = nullptr) : Window(desc), filename_editbox(64), abstract_filetype(abstract_filetype), fop(fop), filter_editbox(EDITBOX_MAX_SIZE) { assert(this->fop == SLO_SAVE || this->fop == SLO_LOAD); + this->veh = veh; + /* For saving, construct an initial file name. */ if (this->fop == SLO_SAVE) { switch (this->abstract_filetype) { @@ -325,6 +428,7 @@ public: case FT_SCENARIO: case FT_HEIGHTMAP: + case FT_ORDERLIST: this->filename_editbox.text.Assign("UNNAMED"); break; @@ -355,6 +459,10 @@ public: caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_HEIGHTMAP : STR_SAVELOAD_LOAD_HEIGHTMAP; break; + case FT_ORDERLIST: + caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_ORDERLIST : STR_SAVELOAD_LOAD_ORDERLIST; + break; + default: NOT_REACHED(); } @@ -367,9 +475,9 @@ public: this->querystrings[WID_SL_FILTER] = &this->filter_editbox; this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR; - /* pause is only used in single-player, non-editor mode, non-menu mode. It + /* pause is only used in single-player, non-editor mode, non-menu mode, when not operationg on orderlists. It * will be unpaused in the WE_DESTROY event handler. */ - if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) { + if (_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR && this->abstract_filetype != FT_ORDERLIST) { DoCommandP(0, PM_PAUSED_SAVELOAD, 1, CMD_PAUSE); } SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); @@ -393,6 +501,10 @@ public: o_dir.name = FioFindDirectory(HEIGHTMAP_DIR); break; + case FT_ORDERLIST: + o_dir.name = FioFindDirectory(ORDERLIST_DIR); + break; + default: o_dir.name = _personal_dir; } @@ -643,12 +755,26 @@ public: case WID_SL_LOAD_BUTTON: { if (this->selected == nullptr || _load_check_data.HasErrors()) break; - + _file_to_saveload.Set(*this->selected); if (this->abstract_filetype == FT_HEIGHTMAP) { this->Close(); ShowHeightmapLoad(); + }else if (this->abstract_filetype == FT_ORDERLIST) { + + FILE * file = FioFOpenFile(this->selected->name, "r", NO_DIRECTORY); + + if (file != nullptr) { + std::ifstream t(file); + std::stringstream buffer; + buffer << t.rdbuf(); + + veh->orders->FromJSONString(veh, buffer.str()); + + this->Close(); + } + } else if (!_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility != GLC_NOT_FOUND || _settings_client.gui.UserIsAllowedToChangeNewGRFs()) { _switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD_GAME; ClearErrorMessages(); @@ -672,6 +798,8 @@ public: break; case WID_SL_DRIVES_DIRECTORIES_LIST: { // Click the listbox + printf("test"); + auto it = this->vscroll->GetScrolledItemFromWidget(this->display_list, pt.y, this, WID_SL_DRIVES_DIRECTORIES_LIST, WidgetDimensions::scaled.inset.top); if (it == this->display_list.end()) return; @@ -685,13 +813,18 @@ public: } if (click_count == 1) { + if (this->selected != file) { + this->selected = file; _load_check_data.Clear(); - if (GetDetailedFileType(file->type) == DFT_GAME_FILE) { - /* Other detailed file types cannot be checked before. */ + auto type = GetDetailedFileType(file->type); + + if(GetDetailedFileType(file->type) == DFT_GAME_FILE) { + SaveOrLoad(file->name, SLO_CHECK, DFT_GAME_FILE, NO_DIRECTORY, false); + } this->InvalidateData(SLIWD_SELECTION_CHANGES); @@ -704,7 +837,7 @@ public: } else if (!_load_check_data.HasErrors()) { this->selected = file; if (this->fop == SLO_LOAD) { - if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO) { + if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO || this->abstract_filetype == FT_ORDERLIST) { this->OnClick(pt, WID_SL_LOAD_BUTTON, 1); } else { assert(this->abstract_filetype == FT_HEIGHTMAP); @@ -737,6 +870,18 @@ public: case WID_SL_SAVE_GAME: // Save game /* Note, this is also called via the OSK; and we need to lower the button. */ this->HandleButtonClick(WID_SL_SAVE_GAME); + + if (this->abstract_filetype == FT_ORDERLIST) { + + std::string fileName = FiosMakeOrderListName(this->filename_editbox.text.buf); + std::ofstream output; + output.open(fileName); + output << this->veh->orders->ToJSONString(); + output.close(); + + this->Close(); + } + break; } } @@ -776,7 +921,7 @@ public: if (this->fop != SLO_SAVE) return; if (this->IsWidgetLowered(WID_SL_DELETE_SELECTION)) { // Delete button clicked - if (!FiosDelete(this->filename_editbox.text.buf)) { + if (!FiosDelete(this->filename_editbox.text.buf, this->abstract_filetype)) { ShowErrorMessage(STR_ERROR_UNABLE_TO_DELETE_FILE, INVALID_STRING_ID, WL_ERROR); } else { this->InvalidateData(SLIWD_RESCAN_FILES); @@ -911,9 +1056,8 @@ public: !_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility == GLC_ALL_GOOD); break; } - - default: - NOT_REACHED(); + + default: break; } break; @@ -948,6 +1092,14 @@ static WindowDesc _load_heightmap_dialog_desc(__FILE__, __LINE__, std::begin(_nested_load_heightmap_dialog_widgets), std::end(_nested_load_heightmap_dialog_widgets) ); +/** Load orderlist*/ +static WindowDesc _load_orderlist_dialog_desc(__FILE__, __LINE__, + WDP_CENTER, "load_orderlist", 257, 320, + WC_SAVELOAD, WC_NONE, + 0, + std::begin(_nested_load_orderlist_dialog_widgets), std::end(_nested_load_orderlist_dialog_widgets) +); + /** Save game/scenario */ static WindowDesc _save_dialog_desc(__FILE__, __LINE__, WDP_CENTER, "save_game", 500, 294, @@ -956,22 +1108,38 @@ static WindowDesc _save_dialog_desc(__FILE__, __LINE__, std::begin(_nested_save_dialog_widgets), std::end(_nested_save_dialog_widgets) ); +/** Load orderlist*/ +static WindowDesc _save_orderlist_dialog_desc(__FILE__, __LINE__, + WDP_CENTER, "save_orderlist", 257, 320, + WC_SAVELOAD, WC_NONE, + 0, + std::begin(_nested_save_orderlist_dialog_widgets), std::end(_nested_save_orderlist_dialog_widgets) +); + /** * Launch save/load dialog in the given mode. * @param abstract_filetype Kind of file to handle. * @param fop File operation to perform (load or save). */ -void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop) +void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fop,const Vehicle * veh) { CloseWindowById(WC_SAVELOAD, 0); WindowDesc *sld; - if (fop == SLO_SAVE) { + if (abstract_filetype == FT_ORDERLIST){ + + if (fop == SLO_SAVE) { + sld = &_save_orderlist_dialog_desc; + } else if (fop == SLO_LOAD) { + sld = &_load_orderlist_dialog_desc; + } + + } else if (fop == SLO_SAVE) { sld = &_save_dialog_desc; } else { /* Dialogue for loading a file. */ sld = (abstract_filetype == FT_HEIGHTMAP) ? &_load_heightmap_dialog_desc : &_load_dialog_desc; } - new SaveLoadWindow(sld, abstract_filetype, fop); + new SaveLoadWindow(sld, abstract_filetype, fop, veh); } diff --git a/src/lang/english.txt b/src/lang/english.txt index 529743ab61..65a11f095a 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3257,8 +3257,10 @@ STR_SAVELOAD_SAVE_CAPTION :{WHITE}Save Gam STR_SAVELOAD_LOAD_CAPTION :{WHITE}Load Game STR_SAVELOAD_SAVE_SCENARIO :{WHITE}Save Scenario STR_SAVELOAD_LOAD_SCENARIO :{WHITE}Load Scenario -STR_SAVELOAD_LOAD_HEIGHTMAP :{WHITE}Load Heightmap STR_SAVELOAD_SAVE_HEIGHTMAP :{WHITE}Save Heightmap +STR_SAVELOAD_LOAD_HEIGHTMAP :{WHITE}Load Heightmap +STR_SAVELOAD_SAVE_ORDERLIST :{WHITE}Save Orderlist +STR_SAVELOAD_LOAD_ORDERLIST :{WHITE}Load Orderlist STR_SAVELOAD_HOME_BUTTON :{BLACK}Click here to jump to the current default save/load directory STR_SAVELOAD_BYTES_FREE :{BLACK}{BYTES} free STR_SAVELOAD_LIST_TOOLTIP :{BLACK}List of drives, directories and saved-game files @@ -5854,3 +5856,8 @@ STR_PLANE :{BLACK}{PLANE} STR_SHIP :{BLACK}{SHIP} STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELOCITY}) + +#json messages + +STR_ERROR_ORDERLIST_MALFORMED_JSON :{WHITE}Input JSON was malformed +STR_ERROR_JON :{WHITE}JSON error diff --git a/src/lang/extra/english.txt b/src/lang/extra/english.txt index 51baceb530..7356aae314 100644 --- a/src/lang/extra/english.txt +++ b/src/lang/extra/english.txt @@ -1687,6 +1687,8 @@ STR_ORDER_REVERSE_ORDER_LIST :Reverse order l STR_ORDER_APPEND_REVERSED_ORDER_LIST :Append reversed order list STR_ORDER_DUPLICATE_ORDER :Duplicate order STR_ORDER_CHANGE_JUMP_TARGET :Change jump target +STR_ORDER_EXPORT_ORDER_LIST :Export orderlist +STR_ORDER_IMPORT_ORDER_LIST :Import orderlist STR_ORDER_TRY_ACQUIRE_SLOT_BUTTON :Try acquire slot STR_ORDER_RELEASE_SLOT_BUTTON :Release slot diff --git a/src/order_base.h b/src/order_base.h index 640b2edf7a..cd6b56c078 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -233,6 +233,9 @@ public: void MakeChangeCounter(); void MakeLabel(OrderLabelSubType subtype); + std::string ToJSONString() const; + static Order FromJSONString(std::string jsonSTR); + /** * Is this a 'goto' order with a real destination? * @return True if the type is either #OT_GOTO_WAYPOINT, #OT_GOTO_DEPOT or #OT_GOTO_STATION. @@ -780,6 +783,7 @@ public: inline std::vector &GetScheduledDispatchMutable() { return this->scheduled_dispatch; } void SetScheduledDispatch(std::vector dispatch_list); + void AddScheduledDispatch(uint32_t offset); void RemoveScheduledDispatch(uint32_t offset); void AdjustScheduledDispatch(int32_t adjust); @@ -856,6 +860,19 @@ public: */ inline int32_t GetScheduledDispatchDelay() const { return this->scheduled_dispatch_max_delay; } + /** + * Get the scheduled dispatch flags + * @return flags + */ + inline int8_t GetScheduledDispatchFlags() const {return this->scheduled_dispatch_flags; } + + /** + * Set the scheduled disaptch flags + * @param flags + */ + inline void SetScheduledDispatchFlags(int8_t flags) { this->scheduled_dispatch_flags = flags; } + + inline void BorrowSchedule(DispatchSchedule &other) { this->CopyBasicFields(other); @@ -869,12 +886,16 @@ public: inline std::string &ScheduleName() { return this->name; } inline const std::string &ScheduleName() const { return this->name; } + + static DispatchSchedule FromJSONString(std::string jsonString); + std::string ToJSONString(); }; /** * Shared order list linking together the linked list of orders and the list * of vehicles sharing this order list. */ + struct OrderList : OrderListPool::PoolItem<&_orderlist_pool> { private: friend void AfterLoadVehicles(bool part_of_load); ///< For instantiating the shared vehicle chain @@ -959,6 +980,9 @@ public: void DeleteOrderAt(int index); void MoveOrder(int from, int to); + std::string ToJSONString(); + static void FromJSONString(const Vehicle* v,std::string str); + /** * Is this a shared order list? * @return whether this order list is shared among multiple vehicles @@ -1025,7 +1049,7 @@ public: void DebugCheckSanity() const; #endif bool CheckOrderListIndexing() const; - + inline std::vector &GetScheduledDispatchScheduleSet() { return this->dispatch_schedules; } inline const std::vector &GetScheduledDispatchScheduleSet() const { return this->dispatch_schedules; } diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 2714fed430..b08cd03f7a 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -26,6 +26,7 @@ #include "core/container_func.hpp" #include "core/pool_func.hpp" #include "core/random_func.hpp" +#include "core/serialisation.hpp" #include "aircraft.h" #include "roadveh.h" #include "station_base.h" @@ -40,12 +41,16 @@ #include "tracerestrict.h" #include "train.h" #include "date_func.h" +#include "3rdparty/nlohmann/json.hpp" +#include "command_aux.h" +#include "rev.h" #include "table/strings.h" #include "3rdparty/robin_hood/robin_hood.h" - +#include <3rdparty/nlohmann/json.hpp> #include "safeguards.h" +#include /* DestinationID must be at least as large as every these below, because it can * be any of them @@ -269,6 +274,100 @@ void Order::MakeLabel(OrderLabelSubType subtype) this->flags = subtype; } +std::string Order::ToJSONString() const +{ + std::string out; + nlohmann::json json; + json["packed-data"] = this->Pack(); + + json["destination-id"] = this->GetDestination(); + Station * station = Station::GetIfValid(this->GetDestination()); + if(station != nullptr) + json["destination-name"] = station->cached_name; + + if(this->extra.get() != nullptr){ + auto& extraJson = json["extra"]; + + extraJson["cargo-type-flags"] = this->extra.get()->cargo_type_flags; + extraJson["colour"] = this->extra.get()->colour; + extraJson["dispatch-index"] = this->extra.get()->dispatch_index; + extraJson["xdata"] = this->extra.get()->xdata; + extraJson["xdata2"] = this->extra.get()->xdata2; + extraJson["xflags"] = this->extra.get()->xflags; + } + + json["refit-cargo"] = this->GetRefitCargo(); + json["wait-time"] = this->GetWaitTime(); + json["travel-time"] = this->GetTravelTime(); + json["max-speed"] = this->GetMaxSpeed(); + + out = json.dump(); + return out; +} + +Order Order::FromJSONString(std::string jsonSTR) +{ + nlohmann::json json = nlohmann::json::parse(jsonSTR); + + if (!json.contains("packed-data") && json["packed_data"].is_number_integer()) { + + Order errOrder; + + errOrder.MakeLabel(OLST_TEXT); + errOrder.SetColour(COLOUR_RED); + errOrder.SetLabelText("JSON_ERR: JSON does not contain mandatory 'packed-data' field for this order"); + + return errOrder; + } + + Order new_order = Order(json.at("packed-data").get()); + + if (json.contains("destination-id") && json["destination-id"].is_number_integer()) + json["destination-id"].get_to(new_order.dest); + + + + if (json.contains("extra") && json["extra"].is_object()) { + auto &extraJson = json["extra"]; + + new_order.AllocExtraInfo(); + + if (extraJson.contains("cargo-type-flags") && extraJson["cargo-type-flags"].is_array()) + for (int i = 0; i < 64; i++) + extraJson["cargo-type-flags"][i].get_to(new_order.extra->cargo_type_flags[i]); + + if (extraJson.contains("colour")) + extraJson["colour"].get_to(new_order.extra->colour); + + if (extraJson.contains("dispatch-index")) + extraJson["dispatch-index"].get_to(new_order.extra->dispatch_index); + + if (extraJson.contains("xdata")) + extraJson["xdata"].get_to(new_order.extra->xdata); + + if (extraJson.contains("xdata2")) + extraJson["xdata2"].get_to(new_order.extra->xdata2); + + if (extraJson.contains("xflags")) + extraJson["xflags"].get_to(new_order.extra->xflags); + + } + + if (json.contains("refit-cargo")) + json["refit-cargo"].get_to(new_order.refit_cargo); + + if (json.contains("wait-time")) + json["wait-time"].get_to(new_order.wait_time); + + if (json.contains("travel-time")) + json["travel-time"].get_to(new_order.travel_time); + + if (json.contains("max-speed")) + json["max-speed"].get_to(new_order.max_speed); + + return new_order; +} + /** * Make this depot/station order also a refit order. * @param cargo the cargo type to change to. @@ -824,6 +923,85 @@ void OrderList::MoveOrder(int from, int to) this->ReindexOrderList(); } +std::string OrderList::ToJSONString() +{ + + + nlohmann::json json; + + json["version"] = ORDERLIST_JSON_OUTPUT_VERSION; + json["source"] = std::string(_openttd_revision); + + if (this == nullptr) { //order list not intiailised, return an empty result + json["error"] = "Orderlist was not initialised"; + return json.dump(); + }; + + auto& SD_data = this->GetScheduledDispatchScheduleSet(); + auto& headJson = json["head"]; + for (unsigned int i = 0; auto &SD : SD_data) { + + headJson["scheduled-dispatch"][i++] = nlohmann::json::parse(SD.ToJSONString()); + + } + + const Order* o = this->GetFirstOrder(); + + if (o != nullptr) { + int i = 0; + do { + json["orders"][i++] = nlohmann::json::parse(o->ToJSONString()); + } while ((o = this->GetNext(o)) != this->GetFirstOrder()); + } + + return json.dump(); + +} + +void OrderList::FromJSONString(const Vehicle * v,std::string json_str) +{ + + Order errOrder; + + errOrder.MakeLabel(OLST_TEXT); + errOrder.SetColour(COLOUR_RED); + + nlohmann::json json; + try { + json = nlohmann::json::parse(json_str); + } catch(nlohmann::json::parse_error e){ + + ShowErrorMessage(STR_ERROR_JON, STR_ERROR_ORDERLIST_MALFORMED_JSON,WL_ERROR); + return; + } + + //delete all orders before setting the new orders + DoCommandP(v->tile, v->index, v->GetNumOrders(), CMD_DELETE_ORDER | CMD_MSG(STR_ERROR_CAN_T_DELETE_THIS_ORDER)); + + if (json.contains("orders")) { + auto &ordersJson = json["orders"]; + if (ordersJson.is_array()) { + for (nlohmann::json::iterator it = ordersJson.begin(); it != ordersJson.end(); ++it) { + auto &orderJson = it.value(); + OrderID new_orderID = v->GetNumOrders(); + DoCommandPEx(v->tile, v->index, new_orderID, 0, CMD_INSERT_ORDER | CMD_MSG(STR_ERROR_CAN_T_INSERT_NEW_ORDER), nullptr, orderJson.dump().c_str(), nullptr, 0); + } + } + } + + if (json.contains("head")){ + auto &headJson = json["head"]; + if (headJson.contains("scheduled-dispatch")) { + auto &SDJson = headJson["scheduled-dispatch"]; + if (SDJson.is_array()) { + for (nlohmann::json::iterator it = SDJson.begin(); it != SDJson.end(); ++it) { + DoCommandPEx(0, v->index, 0, 0, CMD_SCHEDULED_DISPATCH_ADD_NEW_SCHEDULE | CMD_MSG(STR_ERROR_CAN_T_TIMETABLE_VEHICLE), nullptr, it.value().dump().c_str() , 0); + } + } + } + } +} + /** * Removes the vehicle from the shared order list. * @note This is supposed to be called when the vehicle is still in the chain @@ -1017,16 +1195,19 @@ uint GetOrderDistance(const Order *prev, const Order *cur, const Vehicle *v, int * - p2 = (bit 0 - 15) - the selected order (if any). If the last order is given, * the order will be inserted before that one * @param p3 packed order to insert - * @param text unused + * @param orderJson is an optional field for an Order object encoded as JSON * @return the cost of this operation or an error */ -CommandCost CmdInsertOrder(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, uint64_t p3, const char *text, const CommandAuxiliaryBase *aux_data) +CommandCost CmdInsertOrder(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, uint64_t p3, const char *orderJson, const CommandAuxiliaryBase *aux_data) { - VehicleID veh = GB(p1, 0, 20); - VehicleOrderID sel_ord = GB(p2, 0, 16); - Order new_order(p3); + Order new_order = (orderJson != nullptr) ? Order::FromJSONString(orderJson) : Order(p3); + + VehicleOrderID sel_ord = GB(p2, 0, 16); + VehicleID veh = GB(p1, 0, 20); - return CmdInsertOrderIntl(flags, Vehicle::GetIfValid(veh), sel_ord, new_order, false); + CommandCost ret = CmdInsertOrderIntl(flags, Vehicle::GetIfValid(veh), sel_ord, new_order, false); + + return ret; } /** diff --git a/src/order_gui.cpp b/src/order_gui.cpp index 54cab1f523..46f97a7c4f 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -35,6 +35,7 @@ #include "tracerestrict.h" #include "scope.h" #include "core/backup_type.hpp" +#include "fios.h" #include "widgets/order_widget.h" @@ -633,6 +634,8 @@ enum OrderDropDownID { static const StringID _order_manage_list_dropdown[] = { STR_ORDER_REVERSE_ORDER_LIST, STR_ORDER_APPEND_REVERSED_ORDER_LIST, + STR_ORDER_EXPORT_ORDER_LIST, + STR_ORDER_IMPORT_ORDER_LIST, INVALID_STRING_ID }; @@ -1545,6 +1548,7 @@ private: }; int selected_order; + VehicleID vehicle_id; VehicleOrderID order_over; ///< Order over which another order is dragged, \c INVALID_VEH_ORDER_ID if none. OrderPlaceObjectState goto_type; Scrollbar *vscroll; @@ -3488,6 +3492,8 @@ public: switch (index) { case 0: this->OrderClick_ReverseOrderList(0); break; case 1: this->OrderClick_ReverseOrderList(1); break; + case 2: ShowSaveLoadDialog(FT_ORDERLIST, SLO_SAVE, this->GetVehicle()); break; + case 3: ShowSaveLoadDialog(FT_ORDERLIST, SLO_LOAD, this->GetVehicle()); break; default: NOT_REACHED(); } break; diff --git a/src/order_type.h b/src/order_type.h index 8d2d36ebed..ee988a161c 100644 --- a/src/order_type.h +++ b/src/order_type.h @@ -313,6 +313,8 @@ enum CloneOptions { CO_UNSHARE = 2 }; +const uint8_t ORDERLIST_JSON_OUTPUT_VERSION = 1; + struct Order; struct OrderList; diff --git a/src/schdispatch_cmd.cpp b/src/schdispatch_cmd.cpp index da5a88907e..8dd82c99f1 100644 --- a/src/schdispatch_cmd.cpp +++ b/src/schdispatch_cmd.cpp @@ -20,6 +20,7 @@ #include "settings_type.h" #include "schdispatch.h" #include "vehicle_gui.h" +#include <3rdparty/nlohmann/json.hpp> #include @@ -357,15 +358,15 @@ CommandCost CmdScheduledDispatchClear(TileIndex tile, DoCommandFlag flags, uint3 * @param p1 Vehicle index * @param p2 Duration, in scaled tick * @param p3 Start tick - * @param text unused + * @param text optional JSON string * @return the cost of this operation or an error */ -CommandCost CmdScheduledDispatchAddNewSchedule(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, uint64_t p3, const char *text, const CommandAuxiliaryBase *aux_data) +CommandCost CmdScheduledDispatchAddNewSchedule(TileIndex tile, DoCommandFlag flags, uint32_t p1, uint32_t p2, uint64_t p3, const char *scheduleJson, const CommandAuxiliaryBase *aux_data) { VehicleID veh = GB(p1, 0, 20); Vehicle *v = Vehicle::GetIfValid(veh); - if (v == nullptr || !v->IsPrimaryVehicle() || p2 == 0) return CMD_ERROR; + if (v == nullptr || !v->IsPrimaryVehicle()) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; @@ -376,10 +377,19 @@ CommandCost CmdScheduledDispatchAddNewSchedule(TileIndex tile, DoCommandFlag fla if (flags & DC_EXEC) { v->orders->GetScheduledDispatchScheduleSet().emplace_back(); DispatchSchedule &ds = v->orders->GetScheduledDispatchScheduleSet().back(); - ds.SetScheduledDispatchDuration(p2); - ds.SetScheduledDispatchStartTick((StateTicks)p3); - ds.UpdateScheduledDispatch(nullptr); - SetTimetableWindowsDirty(v, STWDF_SCHEDULED_DISPATCH); + if (scheduleJson == nullptr) { + if (p2 == 0) return CMD_ERROR; + ds.SetScheduledDispatchDuration(p2); + ds.SetScheduledDispatchStartTick((StateTicks)p3); + ds.UpdateScheduledDispatch(nullptr); + SetTimetableWindowsDirty(v, STWDF_SCHEDULED_DISPATCH); + } else { + + DispatchSchedule new_ds = DispatchSchedule::FromJSONString(scheduleJson); + ds.BorrowSchedule(new_ds); + + } + } return CommandCost(); @@ -801,3 +811,56 @@ void DispatchSchedule::UpdateScheduledDispatch(const Vehicle *v) SetTimetableWindowsDirty(v, STWDF_SCHEDULED_DISPATCH); } } + +DispatchSchedule DispatchSchedule::FromJSONString(std::string jsonString) +{ + nlohmann::json json = nlohmann::json::parse(jsonString); + + DispatchSchedule new_schedule; + + if (json.contains("slots")) { + + auto &slotsJson = json.at("slots"); + + if (slotsJson.is_array()) { + for (nlohmann::json::iterator it = slotsJson.begin(); it != slotsJson.end(); ++it) { + + DispatchSlot newDispatchSlot; + + newDispatchSlot.offset = it.value().at("offset").template get(); + newDispatchSlot.flags = it.value().at("flags").template get(); + + new_schedule.GetScheduledDispatchMutable().push_back(newDispatchSlot); + + } + } + } + + new_schedule.ScheduleName() = json.at("name").get(); + new_schedule.SetScheduledDispatchStartTick(json.at("start-tick").get()); + new_schedule.SetScheduledDispatchDuration(json.at("duration").get()); + new_schedule.SetScheduledDispatchDelay(json.at("max-delay").get()); + new_schedule.SetScheduledDispatchFlags(json.at("flags").get()); + + return new_schedule; +} + +std::string DispatchSchedule::ToJSONString() +{ + + nlohmann::json json; + + for (unsigned int i = 0; auto & SD_slot : this->GetScheduledDispatch()) { + auto &slotsJson = json["slots"][i++]; + slotsJson["offset"] = SD_slot.offset; + slotsJson["flags"] = SD_slot.flags; + } + + json["name"] = this->ScheduleName(); + json["start-tick"] = this->GetScheduledDispatchStartTick().value; + json["duration"] = this->GetScheduledDispatchDuration(); + json["max-delay"] = this->GetScheduledDispatchDelay(); + json["flags"] = this->GetScheduledDispatchFlags(); + + return json.dump(); +} diff --git a/src/script/api/script_types.hpp b/src/script/api/script_types.hpp index 86e13b7cb8..f0a5b8f4c6 100644 --- a/src/script/api/script_types.hpp +++ b/src/script/api/script_types.hpp @@ -86,7 +86,7 @@ /* Define all types here, so we don't have to include the whole _type.h maze */ typedef uint BridgeType; ///< Internal name, not of any use for you. -typedef byte CargoID; ///< The ID of a cargo. +typedef uint8_t CargoID; ///< The ID of a cargo. class CommandCost; ///< The cost of a command. typedef uint16_t EngineID; ///< The ID of an engine. typedef uint16_t GoalID; ///< The ID of a goal.