Add Json importer (#46)

add boost-json to dependencies
This commit is contained in:
GenevensiS
2025-06-21 07:53:35 +02:00
committed by GitHub
parent 128bb3867d
commit e45353af9b
27 changed files with 685 additions and 185 deletions
+50 -92
View File
@@ -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;
}