speciesgen/src/speciesgen.cpp

553 lines
17 KiB
C++

/*
speciesgen
Copyright (C) 2022-2023 prisixia
This file is part of speciesgen.
speciesgen is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
speciesgen is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with speciesgen. If not, see <https://www.gnu.org/licenses/>.
*/
#include "speciesgen.h"
//----------------------------------------------------------------------
// main()
//----------------------------------------------------------------------
wxIMPLEMENT_APP(SpeciesGen::App);
namespace SpeciesGen {
//----------------------------------------------------------------------
// App
//----------------------------------------------------------------------
bool App::OnInit() {
if (!wxApp::OnInit()) {
return false;
}
// NOLINTBEGIN(cppcoreguidelines-owning-memory): wxWidgets already manages
// its memory more or less so that there's no need for owning the memory
Frame *frame = new Frame("Species Support Generator", 680, 480);
wxMenu *file_menu = new wxMenu;
wxMenu *help_menu = new wxMenu;
wxMenuBar *menu_bar = new wxMenuBar(wxMB_DOCKABLE);
// NOLINTEND(cppcoreguidelines-owning-memory)
#if wxUSE_FILEDLG
file_menu->Append(TEXT_EXPORT, "&Export weightstages\tCTRL+S",
"Export the weightstages configuration to file.");
file_menu->Append(TEXT_IMPORT, "&Import weightstages\tCTRL+O",
"Import and overwrite the weightstages configuration.");
file_menu->Append(TEXT_IMPORT_APPEND, "&Append weightstages\tCTRL+SHIFT+O",
"Import and append to the weightstages configuration.");
file_menu->AppendSeparator();
#endif // wxUSE_FILEDLG
file_menu->Append(TEXT_QUIT, "&Exit\tALT+X", "Quit this program.");
help_menu->Append(TEXT_ABOUT, "&About");
menu_bar->Append(file_menu, "&File");
menu_bar->Append(help_menu, "&Help");
frame->SetMenuBar(menu_bar);
frame->Show(true);
return true;
}
//----------------------------------------------------------------------
// SpritesPanel
//----------------------------------------------------------------------
/*
SpritesPanel::SpritesPanel(wxWindow *window, int x, int y, int w, int h,
const std::string handlerName)
: wxPanel(window, wxID_ANY, wxPoint(x, y), wxSize(w, h)),
ErrorHandler(handlerName)
{
wxImage::AddHandler(new wxPNGHandler);
wxBoxSizer *row1 = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *row2 = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *topSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(row1, 0, wxLEFT | wxUP | wxEXPAND, 10);
topSizer->Add(row2, 0, wxALL | wxEXPAND, 10);
SetSizer(topSizer);
}
*/
//----------------------------------------------------------------------
// Frame
//----------------------------------------------------------------------
wxBEGIN_EVENT_TABLE(Frame, wxFrame)
#if wxUSE_FILEDLG
EVT_MENU(TEXT_EXPORT, Frame::OnExport)
EVT_MENU(TEXT_IMPORT, Frame::OnImport)
EVT_MENU(TEXT_IMPORT_APPEND, Frame::OnImportAppend)
#endif // wxUSE_FILEDLG
EVT_MENU(TEXT_QUIT, Frame::OnQuit)
EVT_MENU(TEXT_ABOUT, Frame::OnAbout)
#if wxUSE_CLIPBOARD
// EVT_MENU(TEXT_CLIPBOARD_PASTE, Frame::OnPasteFromClipboard)
// EVT_MENU(TEXT_CLIPBOARD_COPY, Frame::OnCopyToClipboard)
// EVT_UPDATE_UI(TEXT_CLIPBOARD_PASTE, Frame::OnUpdatePasteFromClipboard)
// EVT_UPDATE_UI(TEXT_CLIPBOARD_COPY, Frame::OnUpdateCopyToClipboard)
#endif // wxUSE_CLIPBOARD
wxEND_EVENT_TABLE()
// NOLINTBEGIN(cppcoreguidelines-owning-memory): wxWidgets already manages its
// memory more or less so that there's no need for owning the memory
Frame::Frame(const wxString &title, int x, int y)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(x, y),
wxDEFAULT_FRAME_STYLE) {
#ifdef WIN32
// SetIcon(wxICON(IDI_ICON1));
#endif // _WIN32
SetMinSize(wxSize(x, y));
#if wxUSE_STATUSBAR
/*
CreateStatusBar(2);
int *widths = new int[2];
widths[0] = 140;
widths[1] = -1;
SetStatusWidths(2, widths);
delete[] widths;
*/
CreateStatusBar(1);
#endif // wxUSE_STATUSBAR
// TODO: Implement SpritesPanel
m_notebook = new wxNotebook(this, wxID_ANY);
m_panel_main = new MainPanel(m_notebook, 10, 10, 0, 0, "Main");
m_panel_stages = new StagesPanel(m_notebook, 10, 10, 0, 0, "Stages");
// m_panel_sprites = new SpritesPanel(m_notebook, 10, 10, 300, 100);
// m_panel_main->GetSizer()->Fit(this);
m_notebook->AddPage(m_panel_main, "Main");
m_notebook->AddPage(m_panel_stages, "Stages");
// m_notebook->AddPage(m_panel_sprites, "Sprites");
wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(m_notebook, 1, wxALL | wxEXPAND);
SetSizer(mainSizer);
DoCheckErrors();
m_panel_main->m_generate->Bind(wxEVT_BUTTON, &Frame::DoGenerateMod, this);
m_panel_main->m_author->Bind(wxEVT_TEXT, &Frame::OnCheckErrors, this);
m_panel_main->m_name->Bind(wxEVT_TEXT, &Frame::OnCheckErrors, this);
m_panel_main->m_friendlyName->Bind(wxEVT_TEXT, &Frame::OnCheckErrors, this);
m_panel_main->m_modDestination->Bind(wxEVT_TEXT, &Frame::OnCheckErrors,
this);
m_panel_stages->m_add->Bind(wxEVT_BUTTON, &Frame::OnAddWeightStage, this);
m_panel_stages->m_remove->Bind(wxEVT_BUTTON, &Frame::OnRemoveWeightStage,
this);
m_panel_stages->m_id->Bind(wxEVT_CHECKBOX, &Frame::OnClickCheckboxID, this);
m_panel_stages->m_subAdd->Bind(wxEVT_BUTTON, &Frame::OnAddWeightStageSub,
this);
m_panel_stages->m_subRemove->Bind(wxEVT_BUTTON,
&Frame::OnRemoveWeightStageSub, this);
}
// NOLINTEND(cppcoreguidelines-owning-memory)
#if wxUSE_CLIPBOARD
/*
void Frame::OnPasteFromClipboard(wxCommandEvent &WXUNUSED(event))
{
m_panel_main->DoPasteFromClipboard();
}
void Frame::OnCopyToClipboard(wxCommandEvent &WXUNUSED(event))
{
m_panel_main->DoCopyToClipboard();
}
void Frame::OnUpdatePasteFromClipboard(wxUpdateUIEvent &event)
{
wxClipboardLocker lockClip;
event.Enable(wxTheClipboard->IsSupported(wxDF_TEXT));
}
void Frame::OnUpdateCopyToClipboard(wxUpdateUIEvent &event)
{
event.Enable(m_panel_main->HasSelection());
}
*/
#endif // wxUSE_CLIPBOARD
void Frame::OnClickCheckboxID(wxCommandEvent &event) {
m_panel_stages->DoClickCheckboxID(event.IsChecked());
DoCheckErrors();
}
void Frame::OnAddWeightStage(wxCommandEvent &WXUNUSED(event)) {
m_panel_stages->DoAddWeightStage();
DoCheckErrors();
}
void Frame::OnRemoveWeightStage(wxCommandEvent &WXUNUSED(event)) {
m_panel_stages->DoRemoveWeightStage();
DoCheckErrors();
}
void Frame::OnAddWeightStageSub(wxCommandEvent &WXUNUSED(event)) {
m_panel_stages->DoAddWeightStageSub();
DoCheckErrors();
}
void Frame::OnRemoveWeightStageSub(wxCommandEvent &WXUNUSED(event)) {
m_panel_stages->DoRemoveWeightStageSub();
DoCheckErrors();
}
void Frame::OnQuit(wxCommandEvent &WXUNUSED(event)) { Close(true); }
void Frame::OnAbout(wxCommandEvent &WXUNUSED(event)) {
wxMessageDialog dialog(this, SPECIESGEN_ABOUT_PAGE,
"About Species Support Generator",
wxOK | wxICON_INFORMATION);
dialog.ShowModal();
}
#if wxUSE_FILEDLG
void Frame::OnExport(wxCommandEvent &WXUNUSED(event)) {
DoReadWeightStages(CONFIG_EXPORT);
}
void Frame::OnImport(wxCommandEvent &WXUNUSED(event)) {
DoReadWeightStages(CONFIG_IMPORT);
}
void Frame::OnImportAppend(wxCommandEvent &WXUNUSED(event)) {
DoReadWeightStages(CONFIG_IMPORT_APPEND);
}
void Frame::DoReadWeightStages(int style) {
std::filesystem::path path;
if ((CONFIG_EXPORT & style) == CONFIG_EXPORT) {
OnFileOpen("Export weightstages to JSON",
"JSON file (*.json)|*.json|Any file (*.*)|*.*", wxFD_SAVE,
path);
if (!path.empty()) {
const std::vector<Starpounds::WeightStage> stages =
m_panel_stages->GetWeightStages();
std::ofstream file_stages(path);
cereal::JSONOutputArchive archive(file_stages);
cereal::Save(archive, stages);
}
} else {
OnFileOpen("Import weightstages from JSON",
"JSON file (*.json)|*.json|Any file (*.*)|*.*",
wxFD_DEFAULT_STYLE | wxFD_FILE_MUST_EXIST, path);
if (!path.empty()) {
std::vector<Starpounds::WeightStage> stages;
{
std::ifstream ifs(path);
cereal::JSONInputArchive archive(ifs);
cereal::Load(archive, stages);
}
if ((CONFIG_IMPORT_APPEND & style) != CONFIG_IMPORT_APPEND) {
m_panel_stages->ClearWeightStages();
}
for (const auto &stage : stages) {
m_panel_stages->DoAddWeightStage(stage);
}
}
}
}
void Frame::OnFileOpen(const std::string &message, const std::string &wildcard,
int style, std::filesystem::path &path) {
const std::filesystem::path path_home =
wxStandardPaths::Get()
.GetUserDir(wxStandardPaths::Dir_Desktop)
.ToStdString();
wxFileDialog dialog(this, message, path_home.string(), wxEmptyString,
wildcard, style);
if (dialog.ShowModal() == wxID_OK) {
path = dialog.GetPath().ToStdString();
}
}
#endif // wxUSE_FILEDLG
bool Frame::DoCheckErrors() {
const size_t errorCount_main = m_panel_main->CheckForErrors();
const size_t errorCount_stages = m_panel_stages->CheckForErrors();
bool hasError = errorCount_main > 0 || errorCount_stages > 0;
if (errorCount_main > 0) {
SetStatusText(m_panel_main->GetError(0));
} else if (errorCount_stages > 0) {
SetStatusText(m_panel_stages->GetError(0));
} else {
SetStatusText("[INFO]: No errors have been found, the generation "
"button has been unlocked.");
}
m_panel_main->m_generate->Enable(!hasError);
return hasError;
}
void Frame::OnCheckErrors(wxCommandEvent &WXUNUSED(event)) { DoCheckErrors(); }
void Frame::DoGenerateMod(wxCommandEvent &WXUNUSED(event)) {
wxBeginBusyCursor();
if (DoCheckErrors()) {
return;
}
for (size_t i = 0; i < m_notebook->GetPageCount(); i++) {
m_notebook->GetPage(i)->Disable();
}
const std::vector<Starpounds::WeightStage> stages =
m_panel_stages->GetWeightStages();
const std::string species = m_panel_main->m_name->GetValue().ToStdString();
const std::string friendlySpecies =
m_panel_main->m_friendlyName->GetValue().ToStdString();
const std::string author = m_panel_main->m_author->GetValue().ToStdString();
const std::filesystem::path path_mod =
m_panel_main->m_modDestination->GetValue().ToStdString() +
wxString::Format("/%ssupport", species).ToStdString();
const std::filesystem::path path_species =
path_mod /
wxString::Format("items/armors/weightstages/%s", species).ToStdString();
std::filesystem::create_directories(path_species);
{
const Starbound::Metadata metadata(
wxString::Format("Starpounds %s species", species).ToStdString(),
wxString::Format("Starpounds - %s species support", friendlySpecies)
.ToStdString(),
author);
const std::filesystem::path path_metadata = path_mod / "_metadata";
if (!std::ifstream(path_metadata).good()) {
std::ofstream file_metadata(path_metadata);
cereal::JSONOutputArchive archive(file_metadata);
cereal::Save(archive, metadata);
}
}
{
const std::filesystem::path path_starpounds =
path_mod / "scripts/starpounds";
std::filesystem::create_directories(path_starpounds);
const Starbound::Patch::Patch patch =
Starbound::Patch::Patch<Starpounds::SpeciesConfig>(
wxString::Format("/%s", species).ToStdString());
const std::filesystem::path path_patch =
path_starpounds / "starpounds_species.config.patch";
if (!std::ifstream(path_patch).good()) {
std::ofstream file_patch(path_patch);
cereal::JSONOutputArchive archive(file_patch);
cereal::Save(archive, std::vector({patch}));
}
}
const std::vector<std::filesystem::path> baseImages = {
"chestf.png", "chestm.png", "bsleevef.png", "bsleevem.png",
"fsleevef.png", "fsleevem.png", "icons.png"};
const std::unordered_map<std::string, std::string> aliases = {
{"swimIdle.2", "swimIdle.1"},
{"swim.5", "swimIdle.1"},
{"swim.6", "swimIdle.2"},
{"swim.7", "swimIdle.2"},
{"lay.1", "idle.1"}};
const std::vector<std::vector<std::optional<std::string>>> names = {
{std::nullopt, "idle.1", "idle.2", "idle.3", "idle.4", "idle.5",
"sit.1", std::nullopt, "duck.1"},
{std::nullopt, "walk.1", "walk.2", "walk.3", "walk.4", "walk.5",
"walk.6", "walk.7", "walk.8"},
{std::nullopt, "run.1", "run.2", "run.3", "run.4", "run.5", "run.6",
"run.7", "run.8"},
{std::nullopt, "jump.1", "jump.2", "jump.3", "jump.4", "fall.1",
"fall.2", "fall.3", "fall.4"},
{std::nullopt, "climb.1", "climb.2", "climb.3", "climb.4", "climb.5",
"climb.6", "climb.7", "climb.8"},
{std::nullopt, "swimIdle.1", std::nullopt, std::nullopt, "swim.1",
"swim.2", "swim.3", "swim.4", std::nullopt}};
const Starbound::Item::Frames maleFrames("chestm.png", "bsleevem.png",
"fsleevem.png");
const Starbound::Item::Frames femaleFrames("chestf.png", "bsleevef.png",
"fsleevef.png");
for (const Starpounds::WeightStage &stage : stages) {
const std::string baseShortDescription =
(stage.GetFriendlyName().empty()
? friendlySpecies
: wxString::Format("%s %s", stage.GetFriendlyName(),
friendlySpecies)
.ToStdString());
const std::string stageName = stage.GetName();
const std::filesystem::path path_stage = path_species / stageName;
if (!std::filesystem::exists(path_stage)) {
std::filesystem::create_directory(path_stage);
}
if (stage.HasFrames()) {
const Starbound::Frames::Frames frame(
aliases, Starbound::Frames::Grid(names));
for (const auto &pants : {"pantsf", "pantsm"}) {
const std::filesystem::path path_frame =
path_species /
wxString::Format("%s_%s.frames", stageName, pants)
.ToStdString();
if (!std::ifstream(path_frame).good()) {
std::ofstream file_frame(path_frame);
cereal::JSONOutputArchive archive(file_frame);
cereal::Save(archive, frame);
}
}
}
if (stage.HasID()) {
std::vector<std::filesystem::path> _baseImages = baseImages;
std::filesystem::path path_pantsf = "pantsf.png";
std::filesystem::path path_pantsm = "pantsm.png";
if (stage.HasFrames()) {
path_pantsf =
wxString::Format("%s_pantsf.png", stageName).ToStdString();
path_pantsm =
wxString::Format("%s_pantsm.png", stageName).ToStdString();
}
_baseImages.push_back(path_pantsf);
_baseImages.push_back(path_pantsm);
for (const std::filesystem::path &image : _baseImages) {
if (!std::ifstream(path_stage / image).good()) {
std::ofstream(path_stage / image);
}
}
const std::filesystem::path path_chest =
path_stage / wxString::Format("%s%s.chest", stageName, species)
.ToStdString();
const std::filesystem::path path_leg =
path_stage /
wxString::Format("%s%s.legs", stageName, species).ToStdString();
const auto typeChest = magic_enum::enum_name(stage.GetChestType());
const auto typeLeg = magic_enum::enum_name(stage.GetLegType());
const Starbound::Item::Item item_chest(
wxString::Format("%s%schest", stageName, species).ToStdString(),
"icons.png:chest", Starbound::Item::Category::Chestwear,
stage.GetChestDescription(),
wxString::Format("%s %s", baseShortDescription,
static_cast<std::string>(typeChest))
.ToStdString(),
maleFrames, femaleFrames);
const Starbound::Item::Item item_leg(
wxString::Format("%s%slegs", stageName, species).ToStdString(),
"icons.png:pants", Starbound::Item::Category::Legwear,
stage.GetLegDescription(),
wxString::Format("%s %s", baseShortDescription,
static_cast<std::string>(typeLeg))
.ToStdString(),
Starbound::Item::Frames(path_pantsm),
Starbound::Item::Frames(path_pantsf));
if (!std::ifstream(path_chest).good()) {
std::ofstream file_chest(path_chest);
cereal::JSONOutputArchive archive(file_chest);
cereal::Save(archive, item_chest);
}
if (!std::ifstream(path_leg).good()) {
std::ofstream file_leg(path_leg);
cereal::JSONOutputArchive archive(file_leg);
cereal::Save(archive, item_leg);
}
}
const std::vector<Starpounds::WeightStageSub> subs = stage.GetSubs();
if (!subs.empty()) {
for (const Starpounds::WeightStageSub &sub : subs) {
const std::string subStageName = sub.GetName();
const std::string stageName =
(stage.HasID() ? stage.GetName() : "");
const std::filesystem::path path_subStage =
path_stage / subStageName;
if (!std::filesystem::exists(path_subStage)) {
std::filesystem::create_directory(path_subStage);
}
for (const std::filesystem::path &image : baseImages) {
if (!std::ifstream(path_subStage / image).good()) {
std::ofstream(path_subStage / image);
}
}
const std::filesystem::path path_chest =
path_subStage.string() +
wxString::Format("/%s%s%s.chest", stageName, subStageName,
species)
.ToStdString();
const Starbound::Item::Item item_chest(
wxString::Format("%s%s%schest", stageName, subStageName,
species)
.ToStdString(),
"icons.png:chest", Starbound::Item::Category::Chestwear,
sub.GetChestDescription(),
wxString::Format(
"%s %s %s", sub.GetFriendlyName(), baseShortDescription,
static_cast<std::string>(
magic_enum::enum_name(stage.GetChestType())))
.ToStdString(),
maleFrames, femaleFrames);
if (!std::ifstream(path_chest).good()) {
std::ofstream file_chest(path_chest);
cereal::JSONOutputArchive archive(file_chest);
cereal::Save(archive, item_chest);
}
}
}
}
for (size_t i = 0; i < m_notebook->GetPageCount(); i++) {
m_notebook->GetPage(i)->Enable(true);
}
SetStatusText("[INFO]: Done, your mod has been successfully created!");
wxEndBusyCursor();
}
} // namespace SpeciesGen