mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
@@ -21,11 +21,11 @@ On windows, the program can be compiled with Visual Studio (recommended) or with
|
||||
|
||||
=======
|
||||
````
|
||||
.\vcpkg install pkgconf wxwidgets[fonts] boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator hunspell --triplet=x64-windows-static
|
||||
.\vcpkg install pkgconf wxwidgets[fonts] boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator boost-json hunspell --triplet=x64-windows-static
|
||||
````
|
||||
and/or
|
||||
````
|
||||
.\vcpkg install pkgconf wxwidgets boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator hunspell --triplet=x86-windows-static
|
||||
.\vcpkg install pkgconf wxwidgets boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator boost-json hunspell --triplet=x86-windows-static
|
||||
````
|
||||
then, regardless of your choice
|
||||
````
|
||||
|
||||
@@ -841,6 +841,9 @@ error:
|
||||
can't create file stream: Failed to create file stream: '%s'
|
||||
can't write image to set: Failed to write image to set: '%s'
|
||||
can't import image: Failed to import image: '%s'
|
||||
import missing fields:
|
||||
The %s file contains the following entries that could not
|
||||
be imported, because no corresponding card field was found: %s
|
||||
|
||||
# Card creation
|
||||
no field with name: Card doesn't have a field named '%s'
|
||||
|
||||
@@ -12,6 +12,7 @@ NOTE: you should use underscores instead of spaces in field names.
|
||||
--Parameters--
|
||||
! Parameter Type Description
|
||||
| @input@ [[type:map]] of field names to field values Field values to set
|
||||
| @ignore_field_not_found@ [[type:boolean]] Optional. If set to true, key/value pairs that don't correspond to a card field will be ignored instead of throwing.
|
||||
|
||||
--Examples--
|
||||
> # Create a new card
|
||||
|
||||
+2
-2
@@ -43,10 +43,10 @@ Fields are part of the [[file:style triangle]]:
|
||||
| @card list name@ [[type:localized string]] field name Alternate name to use for the card list, for example an abbreviation.
|
||||
| @card list alignment@ [[type:alignment]] @left@ Alignment of the card list column.
|
||||
| @sort script@ [[type:script]] Alternate way to sort the card list when using this column to sort the list.
|
||||
| @construction script@ [[type:script]] Script applied to the value given when creating a card with the new_card function.
|
||||
| @import script@ [[type:script]] Script applied to the value given when creating a card with the new_card function. The script may return a map from field names to values.
|
||||
For example, the pt field should not be initialized directly, since it is a combination of the power field and toughness field.
|
||||
So if a value is given for pt, it must be redirected to power and toughness like so: {split := split_text(value, match:"/"); [power:split[0], toughness:split[1]]}.
|
||||
The script must return a map from field names to values. Use the make_map function to dynamically create maps.
|
||||
Use the make_map function to dynamically create maps.
|
||||
|
||||
The @type@ determines what values of this field contain:
|
||||
! Type Values contain Displayed as
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ Such a package contains a [[file:format|data file]] called <tt>game</tt> that ha
|
||||
| @default set style@ [[type:indexmap]] of [[type:style]]s Default style for the set fields, can be overridden by the stylesheet.
|
||||
| @card fields@ [[type:list]] of [[type:field]]s Fields for each card.
|
||||
| @card list color script@ [[type:script]] from fields Script that determines the color of an item in the card list. <br/>If not set uses the @card list colors@ property of the first card field that has it.
|
||||
| @construction script@ [[type:script]] Script that is applied as the last step in the creation of a card in the new_card function. Must return a map from field names to values. Use the make_map function to dynamically create maps.
|
||||
| @import script@ [[type:script]] Script that is applied as the last step in the creation of a card in the new_card function. Must return a map from field names to values. Use the make_map function to dynamically create maps.
|
||||
| @statistics dimensions@ [[type:list]] of [[type:statistics dimension]]s from fields Dimensions for statistics, a dimension is roughly the same as an axis. <br/>By default all card fields with 'show statistics' set to true are used.
|
||||
| @statistics categories@ [[type:list]] of [[type:statistics category]]s from dimensions DOC_MSE_VERSION: not used since 0.3.6
|
||||
Choices shown on the statistics panel. <br/>By default all statistics dimensions are used.
|
||||
|
||||
+1
-1
@@ -61,5 +61,5 @@ To translate the MSE user interface:
|
||||
* Add new keys for game, stylesheet or symbol font specific keys as described above.
|
||||
* Save the file, select the new locale from Edit->Preferences.
|
||||
* Restart MSE, and make sure everything looks right.
|
||||
* Submit the new locale to the [[http://magicseteditor.sourceforge.net/forum/7|MSE forum].
|
||||
* Submit the new locale as a pull request to the [[https://github.com/MagicSetEditorPacks/Full-Magic-Pack|Full-Magic-Pack repository].
|
||||
* Maintain the locale when new versions of MSE come out. A new version may have new user interface items and therefore new keys.
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ Field::~Field() {}
|
||||
|
||||
void Field::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
sort_script.initDependencies(ctx, dep);
|
||||
construction_script.initDependencies(ctx, dep);
|
||||
import_script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(Field) {
|
||||
@@ -64,7 +64,7 @@ IMPLEMENT_REFLECTION(Field) {
|
||||
REFLECT(card_list_allow);
|
||||
REFLECT_LOCALIZED(card_list_name);
|
||||
REFLECT(sort_script);
|
||||
REFLECT(construction_script);
|
||||
REFLECT(import_script);
|
||||
REFLECT_N("card_list_alignment", card_list_align);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public:
|
||||
LocalizedString card_list_name; ///< Name to use in card list.
|
||||
Alignment card_list_align; ///< Alignment of the card list colummn.
|
||||
OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
|
||||
OptionalScript construction_script; ///< The script to apply to the supplied value, when creating a new card.
|
||||
OptionalScript import_script; ///< The script to apply to the supplied value, when creating a new card.
|
||||
Dependencies dependent_scripts; ///< Scripts that depend on values of this field
|
||||
String package_relative_filename;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ SetP MSE1FileFormat::importSet(const String& filename) {
|
||||
// file version check
|
||||
String format = file.ReadLine();
|
||||
if (format.substr(0,8) != _("MTG Set8")) {
|
||||
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from http:://magicsetedtitor.sourceforge.net\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
|
||||
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from https://magicseteditor.boards.net/page/downloads\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
|
||||
}
|
||||
// read general info
|
||||
set->value<TextValue>(_("title")) .value = file.ReadLine();
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@ IMPLEMENT_REFLECTION(Game) {
|
||||
REFLECT_NO_SCRIPT(default_set_style);
|
||||
REFLECT_NO_SCRIPT(card_fields);
|
||||
REFLECT_NO_SCRIPT(card_list_color_script);
|
||||
REFLECT_NO_SCRIPT(construction_script);
|
||||
REFLECT_NO_SCRIPT(import_script);
|
||||
REFLECT_NO_SCRIPT(json_paths);
|
||||
REFLECT_NO_SCRIPT(statistics_dimensions);
|
||||
REFLECT_NO_SCRIPT(statistics_categories);
|
||||
REFLECT_COMPAT(<308, "pack_item", pack_types);
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@ public:
|
||||
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
|
||||
vector<FieldP> card_fields; ///< Fields on each card
|
||||
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
|
||||
OptionalScript construction_script; ///< Script applied as the last step of the new_card function
|
||||
OptionalScript import_script; ///< Script applied as the last step of the new_card function
|
||||
vector<String> json_paths; ///< Paths inside JSON files to find the card array
|
||||
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
|
||||
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
|
||||
vector<PackTypeP> pack_types; ///< Types of random card packs to generate
|
||||
|
||||
@@ -178,13 +178,13 @@ Settings::Settings()
|
||||
, internal_scale (1.0)
|
||||
, internal_image_extension(true)
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
, updates_url (_("http://magicseteditor.sourceforge.net/updates"))
|
||||
, updates_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
#endif
|
||||
, package_versions_url (_("http://magicseteditor.sourceforge.net/packages"))
|
||||
, installer_list_url (_("http://magicseteditor.sourceforge.net/installers"))
|
||||
, package_versions_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
, installer_list_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
, check_updates (CHECK_IF_CONNECTED)
|
||||
, check_updates_all (true)
|
||||
, website_url (_("http://magicseteditor.sourceforge.net/"))
|
||||
, website_url (_("https://magicseteditor.boards.net/"))
|
||||
, install_type (INSTALL_DEFAULT)
|
||||
{}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ void AboutWindow::onPaint(wxPaintEvent& ev) {
|
||||
draw(dc);
|
||||
}
|
||||
|
||||
const char* MSE_AUTHORS[] = {"Twan van Laarhoven (twanvl)", "Sean Hunt (coppro)", "Alissa Rao (Lymia)", "haganbmj", "CaiCai (247321453)"};
|
||||
const char* MSE_AUTHORS[] = {"Twan van Laarhoven (twanvl)", "Sean Hunt (coppro)", "Alissa Rao (Lymia)", "haganbmj", "CaiCai (247321453)", "Olivier Bocksberger (GenevensiS)" };
|
||||
|
||||
void AboutWindow::draw(DC& dc) {
|
||||
wxSize ws = GetClientSize();
|
||||
|
||||
+78
-61
@@ -10,13 +10,14 @@
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <util/prec.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/set.hpp>
|
||||
#include <data/card.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <gui/add_csv_window.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/action/set.hpp>
|
||||
#include <gui/add_csv_window.hpp>
|
||||
#include <script/functions/construction_helper.hpp>
|
||||
#include <wx/statline.h>
|
||||
|
||||
// ----------------------------------------------------------------------------- : AddCSV
|
||||
@@ -32,6 +33,7 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
|
||||
separator_type->Clear();
|
||||
separator_type->Append(_LABEL_("add card csv tab"));
|
||||
separator_type->Append(_LABEL_("add card csv comma"));
|
||||
separator_type->Append(_LABEL_("add card csv semicolon"));
|
||||
separator_type->SetSelection(0);
|
||||
setSeparatorType();
|
||||
// init sizers
|
||||
@@ -39,7 +41,7 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
|
||||
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||
s->Add(new wxStaticText(this, -1, _LABEL_("add card csv sep")), 0, wxALL, 8);
|
||||
s->Add(separator_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("add card csv file")), 0, wxALL | (wxALL & ~wxTOP), 8);
|
||||
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("add card csv file")), 0, wxALL, 8);
|
||||
s->Add(file_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
|
||||
s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8);
|
||||
@@ -55,7 +57,8 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
|
||||
void AddCSVWindow::setSeparatorType() {
|
||||
int sel = separator_type->GetSelection();
|
||||
if (sel == 0) separator = ' ';
|
||||
else separator = ',';
|
||||
else if (sel == 1) separator = ',';
|
||||
else separator = ';';
|
||||
}
|
||||
|
||||
void AddCSVWindow::onSeparatorTypeChange(wxCommandEvent&) {
|
||||
@@ -109,73 +112,87 @@ std::vector<std::string> AddCSVWindow::readCSVRow(const std::string& row) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// escape " { }
|
||||
for (f = 0; f < fields.size(); ++f) {
|
||||
fields[f] = std::regex_replace(fields[f], std::regex("\""), "\\\"");
|
||||
fields[f] = std::regex_replace(fields[f], std::regex("\\{"), "\\{");
|
||||
fields[f] = std::regex_replace(fields[f], std::regex("\\}"), "\\}");
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
bool AddCSVWindow::readCSV(std::ifstream& in, std::vector<String> headers_out, std::vector<std::vector<ScriptValueP>>& table_out) {
|
||||
// Get the rows
|
||||
vector<std::string> raw_rows;
|
||||
std::string raw_row;
|
||||
while (std::getline(in, raw_row)) {
|
||||
raw_rows.push_back(raw_row);
|
||||
}
|
||||
// Check for quoted new line characters
|
||||
vector<std::string> rows;
|
||||
std::string row = "";
|
||||
for (int y = 0; y < raw_rows.size(); ++y) {
|
||||
row = row + raw_rows[y];
|
||||
int quote_count = 0;
|
||||
std::string::size_type pos = 0;
|
||||
while ((pos = row.find("\"", pos)) != std::string::npos) {
|
||||
++quote_count;
|
||||
++pos;
|
||||
}
|
||||
if (quote_count % 2 == 0) {
|
||||
if (row.find_first_not_of(' ') != std::string::npos) rows.push_back(row);
|
||||
row = "";
|
||||
} else {
|
||||
row = row + "\n";
|
||||
}
|
||||
}
|
||||
if (rows.size() == 0) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_1_("import empty file", _("CSV / TSV")));
|
||||
return false;
|
||||
}
|
||||
// Parse headers
|
||||
std::vector<std::string> headers = readCSVRow(rows[0]);
|
||||
for (int x = 0; x < headers.size(); ++x) {
|
||||
String wxstring(headers[x].c_str(), wxConvUTF8);
|
||||
headers_out.push_back(wxstring);
|
||||
}
|
||||
// Parse rows, add to table
|
||||
for (int y = 0; y < rows.size(); ++y) {
|
||||
auto fields = readCSVRow(rows[y]);
|
||||
std::vector<ScriptValueP> values;
|
||||
for (int x = 0; x < fields.size(); ++x) {
|
||||
String wxstring(fields[x].c_str(), wxConvUTF8);
|
||||
values.push_back(to_script(wxstring));
|
||||
}
|
||||
table_out.push_back(values);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddCSVWindow::onOk(wxCommandEvent&) {
|
||||
/// Perform the import
|
||||
// Read the file, put it into a table
|
||||
auto in = std::ifstream(file_path->GetValue().ToStdString());
|
||||
if (in.fail()) {
|
||||
wxBusyCursor wait;
|
||||
// Read the file
|
||||
auto file = std::ifstream(file_path->GetValue().ToStdString());
|
||||
if (file.fail()) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card csv file not found"));
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
std::vector<std::vector<std::string>> table;
|
||||
std::string row;
|
||||
while (!in.eof()) {
|
||||
std::getline(in, row);
|
||||
if (in.bad() || in.fail()) {
|
||||
break;
|
||||
}
|
||||
auto fields = readCSVRow(row);
|
||||
table.push_back(fields);
|
||||
// Put values into a table
|
||||
std::vector<String> headers;
|
||||
std::vector<std::vector<ScriptValueP>> table;
|
||||
if (!readCSV(file, headers, table)) {
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
// ensure table is square
|
||||
int count = table[0].size();
|
||||
for (int y = 1; y < table.size(); ++y) {
|
||||
if (table[y].size() != count) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_1_("add card csv file malformed", wxString::Format(wxT("%i"), y+1)));
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
// Check for missing fields
|
||||
String missing_fields;
|
||||
check_table_headers(set->game, headers, _("CSV / TSV"), missing_fields);
|
||||
if (missing_fields.size() > 0) {
|
||||
queue_message(MESSAGE_WARNING, _ERROR_2_("import missing fields", _("CSV / TSV"), missing_fields));
|
||||
}
|
||||
// produce script from table
|
||||
std::ostringstream stream;
|
||||
stream << "[";
|
||||
for (int y = 1; y < table.size(); ++y) {
|
||||
stream << "new_card([";
|
||||
for (int x = 0; x < count; ++x) {
|
||||
stream << "\"" << table[0][x] << "\": \"" << table[y][x] << "\"";
|
||||
if (x < count - 1) stream << ",";
|
||||
}
|
||||
stream << "])";
|
||||
if (y < table.size() - 1) stream << ",";
|
||||
}
|
||||
stream << "]";
|
||||
String string(stream.str().c_str(), wxConvUTF8);
|
||||
ScriptP script = parse(string, nullptr, false);
|
||||
Context& ctx = set->getContext();
|
||||
ScriptValueP result = script->eval(ctx, false);
|
||||
// create cards
|
||||
// Produce cards from the table
|
||||
vector<CardP> cards;
|
||||
ScriptValueP it = result->makeIterator();
|
||||
while (ScriptValueP item = it->next()) {
|
||||
CardP card = from_script<CardP>(item);
|
||||
// is this a new card?
|
||||
if (contains(set->cards, card) || contains(cards, card)) {
|
||||
// make copy
|
||||
card = make_intrusive<Card>(*card);
|
||||
}
|
||||
cards.push_back(card);
|
||||
if (!cards_from_table(set, headers, table, true, _("CSV / TSV"), cards)) {
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
// add to set
|
||||
// Add cards to set
|
||||
if (!cards.empty()) {
|
||||
// TODO: change the name of the action somehow
|
||||
set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards));
|
||||
@@ -185,7 +202,7 @@ void AddCSVWindow::onOk(wxCommandEvent&) {
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(AddCSVWindow, wxDialog)
|
||||
EVT_BUTTON(wxID_OK, AddCSVWindow::onOk)
|
||||
EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles)
|
||||
EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange)
|
||||
EVT_BUTTON(wxID_OK, AddCSVWindow::onOk)
|
||||
EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles)
|
||||
EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
@@ -28,11 +28,11 @@ protected:
|
||||
SetP set;
|
||||
char separator;
|
||||
|
||||
bool readCSV(std::ifstream& in, std::vector<String> headers_out, std::vector<std::vector<ScriptValueP>>& table_out);
|
||||
std::vector<std::string> readCSVRow(const std::string& row);
|
||||
|
||||
void setSeparatorType();
|
||||
|
||||
void onSeparatorTypeChange(wxCommandEvent&);
|
||||
void setSeparatorType();
|
||||
|
||||
void onBrowseFiles(wxCommandEvent&);
|
||||
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <util/prec.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/set.hpp>
|
||||
#include <data/card.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <data/action/set.hpp>
|
||||
#include <gui/add_json_window.hpp>
|
||||
#include <script/functions/construction_helper.hpp>
|
||||
#include <wx/statline.h>
|
||||
#include <boost/json/src.hpp>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : AddJSON
|
||||
|
||||
AddJSONWindow::AddJSONWindow(Window* parent, const SetP& set, bool sizer)
|
||||
: wxDialog(parent, wxID_ANY, _TITLE_("add card json"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
, set(set)
|
||||
{
|
||||
// init controls
|
||||
file_path = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
|
||||
file_browse = new wxButton(this, ID_CARD_ADD_JSON_BROWSE, _BUTTON_("browse"));
|
||||
json_type = new wxChoice(this, ID_CARD_ADD_JSON_ARRAY, wxDefaultPosition, wxDefaultSize, 0, nullptr);
|
||||
json_type->Clear();
|
||||
FOR_EACH(type, set->game->json_paths) {
|
||||
int delimiter_pos = type.find("//");
|
||||
json_type->Append(type.substr(0, delimiter_pos).Trim().Trim(false));
|
||||
}
|
||||
json_type->Append(_LABEL_("add card json custom"));
|
||||
json_type->SetSelection(0);
|
||||
card_array_path = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
|
||||
setJSONType();
|
||||
// init sizers
|
||||
if (sizer) {
|
||||
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||
s->Add(new wxStaticText(this, -1, _LABEL_("add card json type")), 0, wxALL, 8);
|
||||
s->Add(json_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
s->Add(new wxStaticText(this, -1, _LABEL_("add card json path")), 0, wxALL & ~wxTOP, 8);
|
||||
s->Add(card_array_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
s->Add(new wxStaticText(this, -1, _LABEL_("add card json file")), 0, wxALL, 8);
|
||||
s->Add(file_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
|
||||
s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8);
|
||||
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
|
||||
s->Add(s2, 0, wxEXPAND | wxALL, 12);
|
||||
file_browse->SetFocus();
|
||||
s->SetSizeHints(this);
|
||||
SetSizer(s);
|
||||
SetSize(500, 110);
|
||||
}
|
||||
}
|
||||
|
||||
static ScriptValueP json_to_script(boost::json::value jv) {
|
||||
if (jv == nullptr) return script_nil;
|
||||
else if (jv.is_null()) return script_nil;
|
||||
else if (jv.is_bool()) return to_script(jv.get_bool());
|
||||
else if (jv.is_double()) return to_script(jv.get_double());
|
||||
else if (jv.is_int64()) {
|
||||
int integer = jv.get_int64();
|
||||
return to_script(integer);
|
||||
}
|
||||
else if (jv.is_uint64()) {
|
||||
int integer = jv.get_uint64();
|
||||
return to_script(integer);
|
||||
}
|
||||
else if (jv.is_string()) {
|
||||
std::string stdstring = boost::json::value_to<std::string>(jv);
|
||||
String wxstring(stdstring.c_str(), wxConvUTF8);
|
||||
return to_script(wxstring);
|
||||
}
|
||||
else if (jv.is_array()) {
|
||||
boost::json::array array = jv.get_array();
|
||||
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
|
||||
for (int i = 0; i < array.size(); ++i) {
|
||||
boost::json::value jvalue = array[i];
|
||||
ScriptValueP value = json_to_script(jvalue);
|
||||
result->value.push_back(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (jv.is_object()) {
|
||||
boost::json::object object = jv.get_object();
|
||||
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
|
||||
for (auto it = object.begin(); it != object.end(); ++it) {
|
||||
boost::json::string_view jview = it->key();
|
||||
std::string_view stdview = std::string_view(jview.data(), jview.size());
|
||||
std::string stdstring = { stdview.begin(), stdview.end() };
|
||||
String key(stdstring.c_str(), wxConvUTF8);
|
||||
boost::json::value jvalue = it->value();
|
||||
ScriptValueP value = json_to_script(jvalue);
|
||||
result->key_value[key] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json unknown type"));
|
||||
return script_nil;
|
||||
}
|
||||
}
|
||||
|
||||
void AddJSONWindow::setJSONType() {
|
||||
int sel = json_type->GetSelection();
|
||||
if (sel == json_type->GetCount() - 1) { // Custom type
|
||||
card_array_path->ChangeValue(wxEmptyString);
|
||||
card_array_path->Enable();
|
||||
}
|
||||
else {
|
||||
String selection = json_type->GetString(sel);
|
||||
FOR_EACH(type, set->game->json_paths) {
|
||||
if (type.starts_with(selection)) {
|
||||
int delimiter_pos = type.find("//");
|
||||
card_array_path->ChangeValue(delimiter_pos + 2 < type.Length() ? type.substr(delimiter_pos + 2).Trim().Trim(false) : wxEmptyString);
|
||||
card_array_path->Enable(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddJSONWindow::onJSONTypeChange(wxCommandEvent&) {
|
||||
setJSONType();
|
||||
}
|
||||
|
||||
void AddJSONWindow::onBrowseFiles(wxCommandEvent&) {
|
||||
wxFileDialog* dlg = new wxFileDialog(this, _TITLE_("add card json file"), settings.default_set_dir, wxEmptyString, _("JSON files|*.json|All files (*.*)|*"), wxFD_OPEN);
|
||||
if (dlg->ShowModal() == wxID_OK) {
|
||||
file_path->SetValue(dlg->GetPath());
|
||||
}
|
||||
}
|
||||
|
||||
bool AddJSONWindow::readJSON(std::ifstream& in, std::vector<String>& headers_out, std::vector<std::vector<ScriptValueP>>& table_out) {
|
||||
// Read file
|
||||
std::string input(std::istreambuf_iterator<char>(in), {});
|
||||
boost::json::value jv;
|
||||
try {
|
||||
jv = boost::json::parse(input);
|
||||
} catch (...) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json failed to parse"));
|
||||
return false;
|
||||
}
|
||||
// Split path into tokens
|
||||
std::string path = card_array_path->GetValue().ToStdString();
|
||||
std::vector<std::string> tokens;
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
while ((pos = path.find(":")) != std::string::npos) {
|
||||
token = path.substr(0, pos);
|
||||
tokens.push_back(token);
|
||||
path.erase(0, pos + 1);
|
||||
}
|
||||
tokens.push_back(path);
|
||||
// Navigate path to card array
|
||||
for (int t = 0; t < tokens.size(); ++t) {
|
||||
if (tokens[t] == "") break;
|
||||
if (!jv.is_object()) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
|
||||
return false;
|
||||
}
|
||||
auto& ov = jv.as_object();
|
||||
jv = ov[tokens[t]];
|
||||
if (jv == nullptr) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!jv.is_array()) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
|
||||
return false;
|
||||
}
|
||||
auto& card_array = jv.as_array();
|
||||
size_t count = card_array.size();
|
||||
if (count == 0) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json empty array"));
|
||||
return false;
|
||||
}
|
||||
auto& card = card_array[0];
|
||||
if (!card.is_object()) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
|
||||
return false;
|
||||
}
|
||||
// Get headers
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto& card = card_array[i].as_object();
|
||||
for (auto it = card.begin(); it != card.end(); ++it) {
|
||||
boost::json::string_view jview = it->key();
|
||||
std::string_view stdview = std::string_view(jview.data(), jview.size());
|
||||
std::string stdstring = { stdview.begin(), stdview.end() };
|
||||
String key(stdstring.c_str(), wxConvUTF8);
|
||||
if (std::find(headers_out.begin(), headers_out.end(), key) == headers_out.end()) {
|
||||
headers_out.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse cards, add to table
|
||||
for (int i = 0; i < count; ++i) {
|
||||
std::vector<ScriptValueP> row;
|
||||
auto& card = card_array[i].as_object();
|
||||
for (int h = 0; h < headers_out.size(); ++h) {
|
||||
auto& value = card[headers_out[h].ToStdString()];
|
||||
row.push_back(json_to_script(value));
|
||||
}
|
||||
table_out.push_back(row);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddJSONWindow::onOk(wxCommandEvent&) {
|
||||
/// Perform the import
|
||||
wxBusyCursor wait;
|
||||
// Read the file
|
||||
auto file = std::ifstream(file_path->GetValue().ToStdString());
|
||||
if (file.fail()) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("add card json file not found"));
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
// Put values into a table
|
||||
std::vector<String> headers;
|
||||
std::vector<std::vector<ScriptValueP>> table;
|
||||
if (!readJSON(file, headers, table)) {
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
// Check for missing fields
|
||||
String missing_fields;
|
||||
check_table_headers(set->game, headers, _("JSON"), missing_fields);
|
||||
if (missing_fields.size() > 0) {
|
||||
queue_message(MESSAGE_WARNING, _ERROR_2_("import missing fields", _("JSON"), missing_fields));
|
||||
}
|
||||
// Produce cards from the table
|
||||
vector<CardP> cards;
|
||||
if (!cards_from_table(set, headers, table, true, _("JSON"), cards)) {
|
||||
EndModal(wxID_ABORT);
|
||||
return;
|
||||
}
|
||||
// Add cards to set
|
||||
if (!cards.empty()) {
|
||||
// TODO: change the name of the action somehow
|
||||
set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards));
|
||||
}
|
||||
// Done
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(AddJSONWindow, wxDialog)
|
||||
EVT_BUTTON(wxID_OK, AddJSONWindow::onOk)
|
||||
EVT_BUTTON(ID_CARD_ADD_JSON_BROWSE, AddJSONWindow::onBrowseFiles)
|
||||
EVT_CHOICE(ID_CARD_ADD_JSON_ARRAY, AddJSONWindow::onJSONTypeChange)
|
||||
END_EVENT_TABLE()
|
||||
@@ -0,0 +1,39 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
#pragma once
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
|
||||
DECLARE_POINTER_TYPE(Set);
|
||||
|
||||
// ----------------------------------------------------------------------------- : AddJSONWindow
|
||||
|
||||
/// A window for selecting a JSON file, and importing cards from it.
|
||||
class AddJSONWindow : public wxDialog {
|
||||
public:
|
||||
AddJSONWindow(Window* parent, const SetP& set, bool sizer);
|
||||
|
||||
protected:
|
||||
DECLARE_EVENT_TABLE();
|
||||
|
||||
wxChoice* json_type;
|
||||
wxTextCtrl* card_array_path, *file_path;
|
||||
wxButton* file_browse;
|
||||
SetP set;
|
||||
|
||||
bool readJSON(std::ifstream& in, std::vector<String>& headers_out, std::vector<std::vector<ScriptValueP>>& table_out);
|
||||
|
||||
void onJSONTypeChange(wxCommandEvent&);
|
||||
void setJSONType();
|
||||
|
||||
void onBrowseFiles(wxCommandEvent&);
|
||||
|
||||
void onOk(wxCommandEvent&);
|
||||
|
||||
};
|
||||
@@ -11,7 +11,8 @@
|
||||
#include <gui/control/card_list_column_select.hpp>
|
||||
#include <gui/set/window.hpp> // for sorting all cardlists in a window
|
||||
#include <gui/util.hpp>
|
||||
#include <gui/add_csv_window.hpp>
|
||||
#include <gui/add_csv_window.hpp>
|
||||
#include <gui/add_json_window.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/field.hpp>
|
||||
#include <data/field/choice.hpp>
|
||||
@@ -190,6 +191,15 @@ bool CardListBase::doAddCSV() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CardListBase::doAddJSON() {
|
||||
AddJSONWindow wnd(this, set, true);
|
||||
if (wnd.ShowModal() == wxID_OK) {
|
||||
// The actual adding is done in this window's onOk function
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : CardListBase : Building the list
|
||||
|
||||
|
||||
@@ -77,7 +77,8 @@ public:
|
||||
bool doCopy() override;
|
||||
bool doPaste() override;
|
||||
bool doDelete() override;
|
||||
bool doAddCSV();
|
||||
bool doAddCSV();
|
||||
bool doAddJSON();
|
||||
|
||||
// --------------------------------------------------- : Set actions
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
|
||||
notes = new TextCtrl(nodes_panel, ID_NOTES, true);
|
||||
collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), Color(), false);
|
||||
collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
|
||||
filter = nullptr;
|
||||
filter = nullptr;
|
||||
editor->next_in_tab_order = card_list;
|
||||
// init sizer for notes panel
|
||||
wxSizer* sn = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -69,12 +69,13 @@ CardsPanel::CardsPanel(Window* parent, int id)
|
||||
add_menu_item_tr(menuCard, ID_CARD_NEXT, nullptr, "next card");
|
||||
add_menu_item_tr(menuCard, ID_CARD_SEARCH, nullptr, "search cards");
|
||||
menuCard->AppendSeparator();
|
||||
add_menu_item_tr(menuCard, ID_CARD_ADD, "card_add", "add_card");
|
||||
insertManyCardsMenu = add_menu_item_tr(menuCard, ID_CARD_ADD_MULT, "card_add_multiple", "add cards");
|
||||
// NOTE: space after "Del" prevents wx from making del an accellerator
|
||||
// otherwise we delete a card when delete is pressed inside the editor
|
||||
// Adding a space never hurts, please keep it just to be safe.
|
||||
add_menu_item(menuCard, ID_CARD_ADD_CSV, "card_add_multiple", _MENU_("add card csv") + _(" "), _HELP_("add card csv"));
|
||||
add_menu_item(menuCard, ID_CARD_ADD_JSON, "card_add_multiple", _MENU_("add card json") + _(" "), _HELP_("add card json"));
|
||||
add_menu_item_tr(menuCard, ID_CARD_ADD, "card_add", "add_card");
|
||||
add_menu_item(menuCard, ID_CARD_REMOVE, "card_del", _MENU_("remove card")+_(" "), _HELP_("remove card"));
|
||||
menuCard->AppendSeparator();
|
||||
auto menuRotate = new wxMenu();
|
||||
@@ -98,7 +99,31 @@ CardsPanel::CardsPanel(Window* parent, int id)
|
||||
menuFormat->Append(insertSymbolMenu);
|
||||
|
||||
toolAddCard = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CardsPanel::updateCardCounts() {
|
||||
if (counts) {
|
||||
if (card_list && set) {
|
||||
int selected = card_list->GetSelectedItemCount();
|
||||
int filtered = card_list->GetItemCount();
|
||||
int total = set->cards.size();
|
||||
if (filtered == total) {
|
||||
counts->SetLabel(_TOOL_2_("card counts 2",
|
||||
wxString::Format(wxT("%i"), selected),
|
||||
wxString::Format(wxT("%i"), total)));
|
||||
}
|
||||
else {
|
||||
counts->SetLabel(_TOOL_3_("card counts 3",
|
||||
wxString::Format(wxT("%i"), selected),
|
||||
wxString::Format(wxT("%i"), filtered),
|
||||
wxString::Format(wxT("%i"), total)));
|
||||
}
|
||||
|
||||
} else {
|
||||
counts->SetLabel(_(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardsPanel::updateNotesPosition() {
|
||||
wxSize editor_size = editor->GetBestSize();
|
||||
@@ -205,9 +230,12 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Filter/search textbox
|
||||
tb->AddSeparator();
|
||||
assert(!filter);
|
||||
filter = new FilterCtrl(tb, ID_CARD_FILTER, _LABEL_("search cards"), _HELP_("search_cards_control"));
|
||||
filter = new FilterCtrl(tb, ID_CARD_FILTER, _TOOL_("search cards"), _HELP_("search cards control"));
|
||||
filter->setFilter(filter_value);
|
||||
tb->AddControl(filter);
|
||||
tb->AddControl(filter);
|
||||
counts = new wxStaticText(tb, ID_CARD_COUNTER, _(""));
|
||||
updateCardCounts();
|
||||
tb->AddControl(counts);
|
||||
tb->Realize();
|
||||
// Menus
|
||||
mb->Insert(2, menuCard, _MENU_("cards"));
|
||||
@@ -224,6 +252,7 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
tb->DeleteTool(ID_CARD_ADD);
|
||||
tb->DeleteTool(ID_CARD_REMOVE);
|
||||
tb->DeleteTool(ID_CARD_ROTATE);
|
||||
tb->DeleteTool(ID_CARD_COUNTER);
|
||||
// remember the value in the filter control, because the card list remains filtered
|
||||
// the control is destroyed by DeleteTool
|
||||
filter_value = filter->getFilterString();
|
||||
@@ -282,7 +311,8 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
updateCardCounts();
|
||||
}
|
||||
|
||||
void CardsPanel::onMenuOpen(wxMenuEvent& ev) {
|
||||
@@ -315,6 +345,9 @@ void CardsPanel::onCommand(int id) {
|
||||
case ID_CARD_ADD_CSV:
|
||||
card_list->doAddCSV();
|
||||
break;
|
||||
case ID_CARD_ADD_JSON:
|
||||
card_list->doAddJSON();
|
||||
break;
|
||||
case ID_CARD_REMOVE:
|
||||
card_list->doDelete();
|
||||
break;
|
||||
|
||||
@@ -87,9 +87,12 @@ private:
|
||||
TextCtrl* notes;
|
||||
HoverButton* collapse_notes;
|
||||
FilterCtrl* filter;
|
||||
String filter_value; // value of filter, need separate variable because the control is destroyed
|
||||
String filter_value; // value of filter, need separate variable because the control is destroyed
|
||||
wxStaticText* counts;
|
||||
bool notes_below_editor;
|
||||
|
||||
|
||||
/// Update card counts
|
||||
void updateCardCounts();
|
||||
/// Move the notes panel below the editor or below the card list
|
||||
void updateNotesPosition();
|
||||
// before Layout, call updateNotesPosition.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <util/prec.hpp>
|
||||
#include <script/functions/functions.hpp>
|
||||
#include <script/functions/util.hpp>
|
||||
#include <script/functions/construction_helper.hpp>
|
||||
#include <data/field.hpp>
|
||||
#include <data/field/text.hpp>
|
||||
#include <data/field/choice.hpp>
|
||||
@@ -20,72 +21,11 @@
|
||||
#include <data/card.hpp>
|
||||
#include <util/error.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Helper functions
|
||||
|
||||
static Value* get_container(GameP& game, CardP& card, String key_name) {
|
||||
// find value container to update
|
||||
IndexMap<FieldP, ValueP>::const_iterator value_it = card->data.find(key_name);
|
||||
if (value_it == card->data.end()) {
|
||||
// look among alternate names
|
||||
map<String, String>::iterator alt_name_it = game->card_fields_alt_names.find(unified_form(key_name));
|
||||
if (alt_name_it != game->card_fields_alt_names.end()) {
|
||||
value_it = card->data.find(alt_name_it->second);
|
||||
}
|
||||
}
|
||||
if (value_it == card->data.end()) {
|
||||
throw ScriptError(_ERROR_1_("no field with name", key_name));
|
||||
}
|
||||
return value_it->get();
|
||||
}
|
||||
|
||||
static void set_container(Value* container, ScriptValueP& value, String key_name) {
|
||||
// set the given value into the container
|
||||
if (TextValue* tvalue = dynamic_cast<TextValue*>(container)) {
|
||||
tvalue->value = value->toString();
|
||||
}
|
||||
else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(container)) {
|
||||
cvalue->value = value->toString();
|
||||
}
|
||||
else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(container)) {
|
||||
pvalue->package_name = value->toString();
|
||||
}
|
||||
else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(container)) {
|
||||
cvalue->value = value->toColor();
|
||||
}
|
||||
else if (ImageValue* ivalue = dynamic_cast<ImageValue*>(container)) {
|
||||
wxFileName fname(static_cast<ExternalImage*>(value.get())->toString());
|
||||
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
|
||||
}
|
||||
else {
|
||||
throw ScriptError(_ERROR_1_("can't set value", key_name));
|
||||
}
|
||||
}
|
||||
|
||||
static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value, String key_name) {
|
||||
// check if the given value is for a built-in field, if found set it and return true
|
||||
key_name = unified_form(key_name);
|
||||
if (key_name == _("notes") || key_name == _("note")) {
|
||||
card->notes = value->toString();
|
||||
return true;
|
||||
} else if (key_name == _("style") || key_name == _("stylesheet") || key_name == _("template")) {
|
||||
if (trim(value->toString()) != wxEmptyString) card->stylesheet = StyleSheet::byGameAndName(*game, value->toString());
|
||||
return true;
|
||||
}
|
||||
// else if (key_name == _("id") || key_name == _("multiverse_id")) {
|
||||
// card->id = value->toString();
|
||||
// return true;
|
||||
//}
|
||||
|
||||
//styling_data;
|
||||
//linked_card;
|
||||
//linked_relation_1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : new_card
|
||||
|
||||
SCRIPT_FUNCTION(new_card) {
|
||||
SCRIPT_PARAM(GameP, game);
|
||||
SCRIPT_PARAM(GameP, game);
|
||||
SCRIPT_OPTIONAL_PARAM_(bool, ignore_field_not_found);
|
||||
// create a new card object
|
||||
CardP new_card = make_intrusive<Card>(*game);
|
||||
// iterate on the given key/value pairs
|
||||
@@ -94,31 +34,42 @@ SCRIPT_FUNCTION(new_card) {
|
||||
ScriptValueP key;
|
||||
while (ScriptValueP value = it->next(&key)) {
|
||||
assert(key);
|
||||
if (key == script_nil) continue;
|
||||
String key_name = key->toString();
|
||||
// check if the given value is for a built-in field
|
||||
if (set_builtin_container(game, new_card, value, key_name)) continue;
|
||||
// find the field value (container) that corresponds to the given value
|
||||
Value* container = get_container(game, new_card, key_name);
|
||||
Value* container = get_container(game, new_card, key_name, ignore_field_not_found);
|
||||
if (container == nullptr) continue;
|
||||
FieldP field = container->fieldP;
|
||||
// if the field has a construction script, set the value and card context variables to be the given value and this card, run script
|
||||
if (field->construction_script) {
|
||||
if (field->import_script) {
|
||||
ScriptValueP ctx_value = ctx.getVariableOpt(SCRIPT_VAR_value);
|
||||
ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card);
|
||||
ctx.setVariable(SCRIPT_VAR_value, value);
|
||||
ctx.setVariable(SCRIPT_VAR_card, to_script(new_card));
|
||||
ScriptValueP script_input = field->construction_script.invoke(ctx, true);
|
||||
// iterate on the key/value pairs given by the script
|
||||
ScriptValueP script_it = script_input->makeIterator();
|
||||
ScriptValueP script_key;
|
||||
while (ScriptValueP script_value = script_it->next(&script_key)) {
|
||||
assert(script_key);
|
||||
String script_key_name = script_key->toString();
|
||||
// check if the script value is for a built-in field
|
||||
if (set_builtin_container(game, new_card, script_value, script_key_name)) continue;
|
||||
// find the field value that corresponds to the script value
|
||||
Value* script_container = get_container(game, new_card, script_key_name);
|
||||
// set the field value to the script value
|
||||
set_container(script_container, script_value, script_key_name);
|
||||
ScriptValueP script_input = field->import_script.invoke(ctx, true);
|
||||
// if the script result is a collection, iterate on the key/value pairs
|
||||
// treat the keys as field names and the values as what to populate those fields with
|
||||
if (script_input->type() == SCRIPT_COLLECTION) {
|
||||
ScriptValueP script_it = script_input->makeIterator();
|
||||
ScriptValueP script_key;
|
||||
while (ScriptValueP script_value = script_it->next(&script_key)) {
|
||||
assert(script_key);
|
||||
if (script_key == script_nil) continue;
|
||||
String script_key_name = script_key->toString();
|
||||
// check if the script value is for a built-in field
|
||||
if (set_builtin_container(game, new_card, script_value, script_key_name)) continue;
|
||||
// find the field value that corresponds to the script value
|
||||
Value* script_container = get_container(game, new_card, script_key_name, ignore_field_not_found);
|
||||
if (script_container == nullptr) continue;
|
||||
// set the field value to the script value
|
||||
set_container(script_container, script_value, script_key_name);
|
||||
}
|
||||
}
|
||||
// if the script result is not a collection, simply set the field value to the script value
|
||||
else {
|
||||
set_container(container, script_input, key_name);
|
||||
}
|
||||
// restore old value and card context variables
|
||||
if (ctx_value) ctx.setVariable(SCRIPT_VAR_value, ctx_value);
|
||||
@@ -130,22 +81,29 @@ SCRIPT_FUNCTION(new_card) {
|
||||
}
|
||||
}
|
||||
// if the game has a construction script, set the card context variable to be this card, run script
|
||||
if (game->construction_script) {
|
||||
if (game->import_script) {
|
||||
ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card);
|
||||
ctx.setVariable(SCRIPT_VAR_card, to_script(new_card));
|
||||
ScriptValueP script_input = game->construction_script.invoke(ctx, true);
|
||||
// iterate on the key/value pairs given by the script
|
||||
ScriptValueP script_it = script_input->makeIterator();
|
||||
ScriptValueP script_key;
|
||||
while (ScriptValueP script_value = script_it->next(&script_key)) {
|
||||
assert(script_key);
|
||||
String script_key_name = script_key->toString();
|
||||
// check if the script value is for a built-in field
|
||||
if (set_builtin_container(game, new_card, script_value, script_key_name)) continue;
|
||||
// find the field value that corresponds to the script value
|
||||
Value* script_container = get_container(game, new_card, script_key_name);
|
||||
// set the field value to the script value
|
||||
set_container(script_container, script_value, script_key_name);
|
||||
ScriptValueP script_input = game->import_script.invoke(ctx, true);
|
||||
if (script_input->type() == SCRIPT_COLLECTION) {
|
||||
// iterate on the key/value pairs given by the script
|
||||
ScriptValueP script_it = script_input->makeIterator();
|
||||
ScriptValueP script_key;
|
||||
while (ScriptValueP script_value = script_it->next(&script_key)) {
|
||||
assert(script_key);
|
||||
if (script_key == script_nil) continue;
|
||||
String script_key_name = script_key->toString();
|
||||
// check if the script value is for a built-in field
|
||||
if (set_builtin_container(game, new_card, script_value, script_key_name)) continue;
|
||||
// find the field value that corresponds to the script value
|
||||
Value* script_container = get_container(game, new_card, script_key_name, ignore_field_not_found);
|
||||
if (script_container == nullptr) continue;
|
||||
// set the field value to the script value
|
||||
set_container(script_container, script_value, script_key_name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("game import script not map"));
|
||||
}
|
||||
// restore old context card
|
||||
if (ctx_card) ctx.setVariable(SCRIPT_VAR_card, ctx_card);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/field/text.hpp>
|
||||
#include <data/field/choice.hpp>
|
||||
#include <data/field/package_choice.hpp>
|
||||
#include <data/field/color.hpp>
|
||||
#include <data/field/image.hpp>
|
||||
#include <data/action/set.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/set.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <data/card.hpp>
|
||||
#include <util/error.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Helper functions
|
||||
|
||||
static Value* get_container(GameP& game, CardP& card, String key_name, bool ignore_field_not_found) {
|
||||
// find value container to update
|
||||
IndexMap<FieldP, ValueP>::const_iterator value_it = card->data.find(key_name);
|
||||
if (value_it == card->data.end()) {
|
||||
// look among alternate names
|
||||
map<String, String>::iterator alt_name_it = game->card_fields_alt_names.find(unified_form(key_name));
|
||||
if (alt_name_it != game->card_fields_alt_names.end()) {
|
||||
value_it = card->data.find(alt_name_it->second);
|
||||
}
|
||||
}
|
||||
if (value_it == card->data.end()) {
|
||||
if (ignore_field_not_found) return nullptr;
|
||||
throw ScriptError(_ERROR_1_("no field with name", key_name));
|
||||
}
|
||||
return value_it->get();
|
||||
}
|
||||
|
||||
static void set_container(Value* container, ScriptValueP& value, String key_name) {
|
||||
// set the given value into the container
|
||||
if (TextValue* tvalue = dynamic_cast<TextValue*>(container)) {
|
||||
tvalue->value = value->toString();
|
||||
}
|
||||
else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(container)) {
|
||||
cvalue->value = value->toString();
|
||||
}
|
||||
else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(container)) {
|
||||
pvalue->package_name = value->toString();
|
||||
}
|
||||
else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(container)) {
|
||||
cvalue->value = value->toColor();
|
||||
}
|
||||
else if (ImageValue* ivalue = dynamic_cast<ImageValue*>(container)) {
|
||||
wxFileName fname(static_cast<ExternalImage*>(value.get())->toString());
|
||||
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
|
||||
}
|
||||
else {
|
||||
throw ScriptError(_ERROR_1_("can't set value", key_name));
|
||||
}
|
||||
}
|
||||
|
||||
static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value, String key_name) {
|
||||
// check if the given value is for a built-in field, if found set it and return true
|
||||
key_name = unified_form(key_name);
|
||||
if (key_name == _("notes") || key_name == _("note")) {
|
||||
card->notes = value->toString();
|
||||
return true;
|
||||
} else if (key_name == _("style") || key_name == _("stylesheet") || key_name == _("template")) {
|
||||
if (trim(value->toString()) != wxEmptyString) card->stylesheet = StyleSheet::byGameAndName(*game, value->toString());
|
||||
return true;
|
||||
}
|
||||
// else if (key_name == _("id") || key_name == _("multiverse_id")) {
|
||||
// card->id = value->toString();
|
||||
// return true;
|
||||
//}
|
||||
|
||||
//styling_data;
|
||||
//linked_card;
|
||||
//linked_relation_1;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_table_headers(GameP& game, std::vector<String>& headers, const String& file_extension, String& missing_fields_out) {
|
||||
if (headers.size() == 0) {
|
||||
queue_message(MESSAGE_ERROR, _("Empty headers given"));
|
||||
return false;
|
||||
}
|
||||
for (int x = 0; x < headers.size(); ++x) {
|
||||
String key_name = headers[x];
|
||||
if ( game->card_fields_alt_names.find(unified_form(key_name)) == game->card_fields_alt_names.end()
|
||||
|| key_name == _("notes")
|
||||
|| key_name == _("note")
|
||||
|| key_name == _("style")
|
||||
|| key_name == _("stylesheet")
|
||||
|| key_name == _("template")
|
||||
|| key_name == _("id")
|
||||
|| key_name == _("uid")
|
||||
|| key_name == _("multiverse_id")
|
||||
|| key_name == _("linked_card")
|
||||
|| key_name == _("linked_card_1")
|
||||
|| key_name == _("linked_card_2")
|
||||
|| key_name == _("linked_card_3")
|
||||
|| key_name == _("linked_card_4")
|
||||
|| key_name == _("linked_relation")
|
||||
|| key_name == _("linked_relation_1")
|
||||
|| key_name == _("linked_relation_2")
|
||||
|| key_name == _("linked_relation_3")
|
||||
|| key_name == _("linked_relation_4")
|
||||
|| key_name == _("link_relation")
|
||||
|| key_name == _("link_relation_1")
|
||||
|| key_name == _("link_relation_2")
|
||||
|| key_name == _("link_relation_3")
|
||||
|| key_name == _("link_relation_4")
|
||||
) {
|
||||
missing_fields_out += _("\n ") + key_name;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cards_from_table(SetP& set, vector<String>& headers, std::vector<std::vector<ScriptValueP>>& table, bool ignore_field_not_found, const String& file_extension, vector<CardP>& cards_out) {
|
||||
// ensure table is square
|
||||
int count = headers.size();
|
||||
for (int y = 0; y < table.size(); ++y) {
|
||||
if (table[y].size() != count) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_2_("import file malformed", file_extension, wxString::Format(wxT("%i"), y+1)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// produce cards from table
|
||||
Context& ctx = set->getContext();
|
||||
ScriptValueP new_card_function = ctx.getVariable("new_card");
|
||||
ScriptValueP ctx_input = ctx.getVariableOpt(SCRIPT_VAR_input);
|
||||
ScriptValueP ctx_ignore = ctx.getVariableOpt("ignore_field_not_found");
|
||||
ctx.setVariable("ignore_field_not_found", to_script(ignore_field_not_found));
|
||||
for (int y = 0; y < table.size(); ++y) {
|
||||
ScriptCustomCollectionP field_map = make_intrusive<ScriptCustomCollection>();
|
||||
for (int x = 0; x < count; ++x) {
|
||||
// check if value is worth writing
|
||||
if (table[y][x] != script_nil) {
|
||||
field_map->key_value[headers[x]] = table[y][x];
|
||||
}
|
||||
}
|
||||
ctx.setVariable(SCRIPT_VAR_input, field_map);
|
||||
CardP card = from_script<CardP>(new_card_function->eval(ctx));
|
||||
// is this a new card?
|
||||
if (contains(set->cards, card) || contains(cards_out, card)) {
|
||||
// make copy
|
||||
card = make_intrusive<Card>(*card);
|
||||
}
|
||||
cards_out.push_back(card);
|
||||
}
|
||||
if (ctx_input) ctx.setVariable(SCRIPT_VAR_input, ctx_input);
|
||||
if (ctx_ignore) ctx.setVariable("ignore_field_not_found", ctx_ignore);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,13 @@ String tr(const String&, const String& subcat, const String& key, DefaultLocaleF
|
||||
/// A localized string for tooltip text, with 1 argument (printf style)
|
||||
#define _HELP_1_(s,a) format_string(_HELP_(s), a)
|
||||
|
||||
/// A localized string for tooltip labels, with 1 argument (printf style)
|
||||
#define _TOOL_1_(s,a) format_string(_TOOL_(s), a)
|
||||
/// A localized string for tooltip labels, with 2 argument (printf style)
|
||||
#define _TOOL_2_(s,a,b) format_string(_TOOL_(s), a, b)
|
||||
/// A localized string for tooltip labels, with 3 argument (printf style)
|
||||
#define _TOOL_3_(s,a,b,c) format_string(_TOOL_(s), a, b, c)
|
||||
|
||||
/// A localized string for tooltip text, with 1 argument (printf style)
|
||||
#define _TOOLTIP_1_(s,a) format_string(_TOOLTIP_(s), a)
|
||||
|
||||
|
||||
@@ -111,6 +111,9 @@ enum ChildMenuID {
|
||||
ID_CARD_ADD_CSV,
|
||||
ID_CARD_ADD_CSV_SEP,
|
||||
ID_CARD_ADD_CSV_BROWSE,
|
||||
ID_CARD_ADD_JSON,
|
||||
ID_CARD_ADD_JSON_ARRAY,
|
||||
ID_CARD_ADD_JSON_BROWSE,
|
||||
|
||||
// Keyword menu
|
||||
ID_KEYWORD_ADD = 6101,
|
||||
@@ -188,6 +191,7 @@ enum ChildMenuID {
|
||||
// On cards panel
|
||||
ID_COLLAPSE_NOTES = 8001,
|
||||
ID_CARD_FILTER,
|
||||
ID_CARD_COUNTER,
|
||||
|
||||
// Style panel
|
||||
ID_STYLE_USE_FOR_ALL = 8011,
|
||||
|
||||
@@ -17,7 +17,7 @@ The MSE source code is subdivided into several directories, with the following m
|
||||
- <tt>gfx</tt>: Graphics related functions, again mostly independent of MSE.
|
||||
This directory contains algorithms for image blending, scaling, and bezier curve functions.
|
||||
- <tt>data</tt>: Data structures, like sets, cards, symbols, etc.
|
||||
These data structures are documented in the <a href="http://magicseteditor.sourceforge.net/extending">'Extending MSE'</a> section of the official documentation.
|
||||
These data structures are documented in the <a href="https://mseverse.miraheze.org/wiki/Dev:Data_types">'Data types'</a> section of the documentation.
|
||||
- <tt>data/action</tt>: Actions that can be applied to those data structures.
|
||||
- <tt>data/field</tt>: Data types for fields, values and styles. One source file per type.
|
||||
- <tt>data/format</tt>: File formats and import/export stuff.
|
||||
@@ -39,4 +39,4 @@ Additional tools (not needed for building MSE) also depend on:
|
||||
- <a href="http://doxygen.org">doxygen</a> for generating the documentation.
|
||||
- Perl for small utility scripts
|
||||
|
||||
*/
|
||||
*/
|
||||
|
||||
@@ -10,4 +10,4 @@ description:
|
||||
<li>Added a "Console" panel for error messages and code evaluation.
|
||||
<li>Fixed several bugs
|
||||
</ul>
|
||||
<a href="http://magicseteditor.sourceforge.net/download">Get version 2.0.0 from the MSE download page</a>.
|
||||
<a href="https://magicseteditor.boards.net/page/downloads">Get version 2.5.8 from the MSE download page</a>.
|
||||
|
||||
Reference in New Issue
Block a user