Add update_cards_scripts

This commit is contained in:
GenevensiS
2026-05-13 18:09:56 +02:00
parent f4fe9ab6b0
commit 151a04909a
26 changed files with 403 additions and 108 deletions
+25
View File
@@ -87,6 +87,31 @@ void AddCardAction::perform(bool to_undo) {
set.buildUIDMap();
}
UpdateCardAction::UpdateCardAction(Set& set, const vector<CardP>& cards_to_add, const vector<CardP>& cards_to_remove)
: CardListAction(set)
, action_add(ADD, set, cards_to_add)
, action_remove(REMOVE, set, cards_to_remove)
{}
String UpdateCardAction::getName(bool to_undo) const {
return _ACTION_("update card");
}
void UpdateCardAction::perform(bool to_undo) {
if (to_undo) {
action_remove.perform(to_undo);
set.actions.tellListeners(action_remove, to_undo);
action_add.perform(to_undo);
set.actions.tellListeners(action_add, to_undo);
}
else {
action_add.perform(to_undo);
set.actions.tellListeners(action_add, to_undo);
action_remove.perform(to_undo);
set.actions.tellListeners(action_remove, to_undo);
}
}
// ----------------------------------------------------------------------------- : Reorder cards
ReorderCardsAction::ReorderCardsAction(Set& set, size_t card_id1, size_t card_id2)
+12
View File
@@ -50,6 +50,18 @@ public:
vector<ActionP> card_link_actions;
};
/// Update one or more cards from a set by replacing them with other cards
class UpdateCardAction : public CardListAction {
public:
UpdateCardAction(Set& set, const vector<CardP>& cards_to_add, const vector<CardP>& cards_to_remove);
String getName(bool to_undo) const override;
void perform(bool to_undo) override;
AddCardAction action_add;
AddCardAction action_remove;
};
// ----------------------------------------------------------------------------- : Reorder cards
/// Change the position of a card in the card list by swapping two cards
+4 -2
View File
@@ -400,8 +400,10 @@ String BulkAction::getName(bool to_undo) const {
return to_undo ? name_undo : name_do;
}
void BulkAction::perform(bool to_undo) {
FOR_EACH(action, actions) {
void BulkAction::perform(bool to_undo) {
size_t size = actions.size();
for (size_t i = 0 ; i < size ; ++i) {
ActionP& action = actions[to_undo ? size - i - 1 : i];
action->perform(to_undo);
set->actions.tellListeners(*action, to_undo);
}
+9 -5
View File
@@ -25,18 +25,22 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(AddCardsScript) {
void AddCardsScript::perform(Set& set, vector<CardP>& out) {
// Perform script
Context& ctx = set.getContext();
Context& ctx = set.getContext();
if (enabled.hasBeenRead()) {
enabled.update(ctx);
if (!enabled()) return;
}
ScriptValueP result = script.invoke(ctx);
// Add cards to out
ScriptValueP it = result->makeIterator();
while (ScriptValueP item = it->next()) {
CardP card = from_script<CardP>(item);
CardP new_card = from_script<CardP>(item);
// is this a new card?
if (contains(set.cards,card) || contains(out,card)) {
if (contains(set.cards,new_card) || contains(out,new_card)) {
// make copy
card = make_intrusive<Card>(*card);
new_card = make_intrusive<Card>(&set, new_card);
}
out.push_back(card);
out.push_back(new_card);
}
}
+38 -2
View File
@@ -42,7 +42,38 @@ Card::Card(Game& game)
, has_styling(false)
{
data.init(game.card_fields);
}
}
Card::Card(Set* set, const CardP& card)
: game(card->game)
, time_created (wxDateTime::Now())
, time_modified(wxDateTime::Now())
, notes(card->notes)
, uid(generate_uid())
, linked_card_1(card->linked_card_1)
, linked_card_2(card->linked_card_2)
, linked_card_3(card->linked_card_3)
, linked_card_4(card->linked_card_4)
, linked_relation_1(card->linked_relation_1)
, linked_relation_2(card->linked_relation_2)
, linked_relation_3(card->linked_relation_3)
, linked_relation_4(card->linked_relation_4)
, has_styling(card->has_styling)
, stylesheet_version(card->stylesheet_version)
, stylesheet(card->stylesheet)
{
if (!stylesheet && set) {
stylesheet = set->stylesheetForP(card);
}
if (has_styling) {
styling_data.cloneFrom(card->styling_data);
}
else {
if (stylesheet && set) styling_data.cloneFrom(set->stylingDataFor(*stylesheet));
}
data.cloneFrom(card->data);
extra_data.cloneFrom(card->extra_data);
}
String Card::identification() const {
// an identifying field
@@ -344,7 +375,12 @@ void reflect_version_check(GetDefaultMember& handler, const Char* key, intrusive
IMPLEMENT_REFLECTION(Card) {
REFLECT(stylesheet);
reflect_version_check(handler, _("stylesheet_version"), stylesheet);
if (Handler::isReading) {
REFLECT_NO_SCRIPT(stylesheet_version);
}
else {
reflect_version_check(handler, _("stylesheet_version"), stylesheet);
}
REFLECT(has_styling);
if (has_styling) {
if (stylesheet) {
+5 -1
View File
@@ -35,6 +35,8 @@ public:
Card();
/// Creates a card using the given game
Card(Game& game);
/// Copy constructor, makes a deep copy
Card(Set* set, const CardP& card);
/// The game this card is made for
Game* game;
@@ -60,7 +62,9 @@ public:
wxDateTime time_created, time_modified;
/// Alternative style to use for this card
/** Optional; if not set use the card style from the set */
StyleSheetP stylesheet;
StyleSheetP stylesheet;
/// What version of the stylesheet was this card using when it was last saved?
Version stylesheet_version;
/// Alternative options to use for this card, for this card's stylesheet
/** Optional; if not set use the styling data from the set.
* If stylesheet is set then contains data for the this->stylesheet, otherwise for set->stylesheet
+6 -3
View File
@@ -16,6 +16,7 @@
#include <data/pack.hpp>
#include <data/word_list.hpp>
#include <data/add_cards_script.hpp>
#include <data/update_cards_script.hpp>
#include <util/io/package_manager.hpp>
#include <script/script.hpp>
@@ -55,7 +56,6 @@ IMPLEMENT_REFLECTION(Game) {
REFLECT_NO_SCRIPT(json_paths);
REFLECT_NO_SCRIPT(statistics_dimensions);
REFLECT_NO_SCRIPT(statistics_categories);
REFLECT_COMPAT(<308, "pack_item", pack_types);
REFLECT_NO_SCRIPT(pack_types);
REFLECT_NO_SCRIPT(keyword_match_script);
REFLECT(has_keywords);
@@ -63,7 +63,8 @@ IMPLEMENT_REFLECTION(Game) {
REFLECT(keyword_parameter_types);
REFLECT_NO_SCRIPT(keywords);
REFLECT_NO_SCRIPT(word_lists);
REFLECT_NO_SCRIPT(add_cards_scripts);
REFLECT_NO_SCRIPT(add_cards_scripts);
REFLECT_NO_SCRIPT(update_cards_scripts);
REFLECT_NO_SCRIPT(auto_replaces);
}
@@ -205,7 +206,9 @@ void Game::validate(Version v) {
}
card_links_alt_names.emplace(linked_tr, linked_default);
}
}
}
// sort the update_cards_scripts from oldest to newest
std::sort(update_cards_scripts.begin(), update_cards_scripts.end());
}
void Game::initCardListColorScript() {
+23 -21
View File
@@ -26,6 +26,7 @@ DECLARE_POINTER_TYPE(KeywordMode);
DECLARE_POINTER_TYPE(Keyword);
DECLARE_POINTER_TYPE(WordList);
DECLARE_POINTER_TYPE(AddCardsScript);
DECLARE_POINTER_TYPE(UpdateCardsScript);
DECLARE_POINTER_TYPE(AutoReplace);
// ----------------------------------------------------------------------------- : Game
@@ -38,27 +39,28 @@ class Game : public Packaged {
public:
Game();
OptionalScript init_script; ///< Script of variables available to other scripts in this game
vector<FieldP> set_fields; ///< Fields for set information
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
vector<CardLinkP> card_links; ///< Possible links between cards
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
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
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
map<String,String> card_fields_alt_names; ///< Other names that fields might go by, for example in CSV files
map<String,String> card_links_alt_names; ///< Localized names that card links go by
bool has_keywords; ///< Does this game use keywords?
OptionalScript keyword_match_script; ///< For the keyword editor
vector<KeywordParamP> keyword_parameter_types;///< Types of keyword parameters
vector<KeywordModeP> keyword_modes; ///< Modes of keywords
vector<KeywordP> keywords; ///< Keywords for use in text
OptionalScript init_script; ///< Script of variables available to other scripts in this game
vector<FieldP> set_fields; ///< Fields for set information
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
vector<CardLinkP> card_links; ///< Possible links between cards
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
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
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
vector<UpdateCardsScriptP> update_cards_scripts; ///< Scripts for updating cards made with an earlier version of this game
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
map<String,String> card_fields_alt_names; ///< Other names that fields might go by, for example in CSV files
map<String,String> card_links_alt_names; ///< Localized names that card links go by
bool has_keywords; ///< Does this game use keywords?
OptionalScript keyword_match_script; ///< For the keyword editor
vector<KeywordParamP> keyword_parameter_types;///< Types of keyword parameters
vector<KeywordModeP> keyword_modes; ///< Modes of keywords
vector<KeywordP> keywords; ///< Keywords for use in text
Dependencies dependent_scripts_cards; ///< scripts that depend on the card list
Dependencies dependent_scripts_keywords; ///< scripts that depend on the keywords
-1
View File
@@ -85,7 +85,6 @@ bool Keyword::contains(QuickFilterPart const& query) const {
IMPLEMENT_REFLECTION(Keyword) {
REFLECT(keyword);
if (handler.formatVersion() < 301) read_compat(handler, this);
REFLECT(match);
REFLECT(reminder);
REFLECT(rules);
+56 -9
View File
@@ -14,6 +14,7 @@
#include <data/keyword.hpp>
#include <data/pack.hpp>
#include <data/field.hpp>
#include <data/update_cards_script.hpp>
#include <data/field/text.hpp> // for 0.2.7 fix
#include <data/field/information.hpp>
#include <data/field/image.hpp>
@@ -172,7 +173,7 @@ void fix_value_207(const ValueP& value) {
void Set::validate(Version file_app_version) {
Packaged::validate(file_app_version);
// are the
// are the game and stylesheet defined?
if (!game) {
throw Error(_ERROR_1_("no game specified",_TYPE_("set")));
}
@@ -183,7 +184,9 @@ void Set::validate(Version file_app_version) {
if (stylesheet->game != game) {
throw Error(_ERROR_("stylesheet and set refer to different game"));
}
// We can probably retire this
/*
// This is our chance to fix version incompatabilities
if (file_app_version < 207) {
// Since 0.2.7 we use </tag> style close tags, in older versions it was </>
@@ -192,16 +195,52 @@ void Set::validate(Version file_app_version) {
FOR_EACH(v, c->data) fix_value_207(v);
}
FOR_EACH(v, data) fix_value_207(v);
/* FOR_EACH(s, styleData) {
FOR_EACH(s, styleData) {
FOR_EACH(v, s.second->data) fix_value_207(v);
}
*/ }
}
*/
// we want at least one card
if (cards.empty()) cards.push_back(make_intrusive<Card>(*game));
// update scripts
script_manager->updateAll();
// build uid map
buildUIDMap();
buildUIDMap();
// update cards with game update_cards_scripts
for (int j = 0; j < game->update_cards_scripts.size(); ++j) {
UpdateCardsScriptP& script = game->update_cards_scripts[j];
if (game_version >= script->before_version) continue;
size_t size = cards.size();
for (int i = 0; i < size; ++i) {
CardP& card = cards[i];
vector<CardP> new_cards = script->perform(*this, card);
if (!new_cards.empty()) {
--i;
--size;
}
}
}
// update cards with stylesheet update_cards_scripts
for (int i = 0; i < cards.size(); ++i) {
CardP& card = cards[i];
StyleSheetP stylesheet = stylesheetForP(card);
for (int j = 0; j < stylesheet->update_cards_scripts.size(); ++j) {
UpdateCardsScriptP& script = stylesheet->update_cards_scripts[j];
if (card->stylesheet_version >= script->before_version) continue;
vector<CardP> new_cards = script->perform(*this, card);
if (!new_cards.empty()) {
FOR_EACH(new_card, new_cards) {
// Initialize the stylesheet_version if it wasn't defined, to prevent this script from applying again
if (stylesheet == stylesheetForP(new_card) && new_card->stylesheet_version < script->before_version) {
new_card->stylesheet_version = script->before_version;
}
}
--i;
break;
}
}
}
}
void reflect_version_check(Reader& handler, const Char* key, intrusive_ptr<Packaged> const& package) {
@@ -225,15 +264,23 @@ IMPLEMENT_REFLECTION(Set) {
REFLECT_IF_READING {
data.init(game->set_fields);
}
reflect_version_check(handler, _("game_version"), game);
if (Handler::isReading) {
REFLECT_NO_SCRIPT(game_version);
}
else {
reflect_version_check(handler, _("game_version"), game);
}
WITH_DYNAMIC_ARG(game_for_reading, game.get());
REFLECT(stylesheet);
REFLECT_COMPAT(<300, "style", stylesheet);
reflect_version_check(handler, _("stylesheet_version"), stylesheet);
if (Handler::isReading) {
REFLECT_NO_SCRIPT(stylesheet_version);
}
else {
reflect_version_check(handler, _("stylesheet_version"), stylesheet);
}
WITH_DYNAMIC_ARG(stylesheet_for_reading, stylesheet.get());
REFLECT_N("set_info", data);
if (stylesheet) {
REFLECT_COMPAT(<300, "extra_set_info", styling_data);
REFLECT_N("styling", styling_data);
}
// Experimental: save each card to a different file
+5 -1
View File
@@ -61,7 +61,11 @@ public:
ActionStack actions; ///< Actions performed on this set and the cards in it
KeywordDatabase keyword_db; ///< Database for matching keywords, must be cleared when keywords change
VCSP vcs; ///< The version control system to use
/// What version of the game was this set using when it was last saved?
Version game_version;
/// What version of the default stylesheet was this set using when it was last saved?
Version stylesheet_version;
/// A context for performing scripts
/** Should only be used from the main thread! */
Context& getContext();
+7 -1
View File
@@ -10,6 +10,7 @@
#include <data/stylesheet.hpp>
#include <data/game.hpp>
#include <data/field.hpp>
#include <data/update_cards_script.hpp>
#include <util/io/package_manager.hpp>
#include <gui/new_window.hpp> // for selecting stylesheets on load error
@@ -75,7 +76,11 @@ void StyleSheet::validate(Version ver) {
throw Error(_ERROR_1_("no game specified",_TYPE_("stylesheet")));
}
// a stylesheet depends on the game it is made for
requireDependency(game.get());
requireDependency(game.get());
// sort the update_cards_scripts from oldest to newest
std::sort(update_cards_scripts.begin(), update_cards_scripts.end(), [](const auto& a, const auto& b) {
return *a < *b;
});
}
@@ -125,6 +130,7 @@ IMPLEMENT_REFLECTION(StyleSheet) {
extra_card_style.init(extra_card_fields);
}
REFLECT(extra_card_style);
REFLECT_NO_SCRIPT(update_cards_scripts);
}
+8 -6
View File
@@ -22,6 +22,7 @@ DECLARE_POINTER_TYPE(StyleSheet);
DECLARE_POINTER_TYPE(Field);
DECLARE_POINTER_TYPE(Style);
DECLARE_POINTER_TYPE(CardRegion);
DECLARE_POINTER_TYPE(UpdateCardsScript);
// ----------------------------------------------------------------------------- : StyleSheet
@@ -33,12 +34,13 @@ class StyleSheet : public Packaged {
public:
StyleSheet();
GameP game; ///< The game this stylesheet is made for
OptionalScript init_script; ///< Script of variables available to other scripts in this stylesheet
double card_width; ///< The width of a card in pixels
double card_height; ///< The height of a card in pixels
double card_dpi; ///< The resolution of a card in dots per inch
Color card_background; ///< The background color of cards
GameP game; ///< The game this stylesheet is made for
OptionalScript init_script; ///< Script of variables available to other scripts in this stylesheet
vector<UpdateCardsScriptP> update_cards_scripts; ///< Scripts for updating cards made with an earlier version of this stylesheet
double card_width; ///< The width of a card in pixels
double card_height; ///< The height of a card in pixels
double card_dpi; ///< The resolution of a card in dots per inch
Color card_background; ///< The background color of cards
vector<CardRegionP> card_regions;
/// The styling for card fields
/** The indices should correspond to the card_fields in the Game */
-1
View File
@@ -178,7 +178,6 @@ IMPLEMENT_REFLECTION(SymbolInFont) {
REFLECT(draw_text);
REFLECT(text_font);
REFLECT(text_alignment);
REFLECT_COMPAT(<300,"text_align",text_alignment);
REFLECT(text_margin_left);
REFLECT(text_margin_right);
REFLECT(text_margin_top);
+57
View File
@@ -0,0 +1,57 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make card games |
//| 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/update_cards_script.hpp>
#include <data/action/set.hpp>
#include <data/set.hpp>
#include <data/card.hpp>
#include <data/stylesheet.hpp>
#include <data/action/value.hpp>
// ----------------------------------------------------------------------------- : UpdateCardsScript
IMPLEMENT_REFLECTION_NO_SCRIPT(UpdateCardsScript) {
REFLECT(before_version);
REFLECT(description);
REFLECT(enabled);
REFLECT(script);
}
void UpdateCardsScript::perform(Set& set, CardP& card, vector<CardP>& out) {
// Perform script
Context& ctx = set.getContext(card);
if (enabled.hasBeenRead()) {
enabled.update(ctx);
if (!enabled()) return;
}
ScriptValueP result = script.invoke(ctx);
// Add cards to out
ScriptValueP it = result->makeIterator();
while (ScriptValueP item = it->next()) {
CardP new_card = from_script<CardP>(item);
// is this a new card?
if (contains(set.cards,new_card) || contains(out,new_card)) {
// make copy
new_card = make_intrusive<Card>(&set, new_card);
}
out.push_back(new_card);
}
}
vector<CardP> UpdateCardsScript::perform(Set& set, CardP card) {
// Perform script
vector<CardP> cards;
perform(set, card, cards);
// Add to set
if (!cards.empty()) {
set.actions.addAction(make_unique<UpdateCardAction>(set, cards, vector<CardP>{card}), false);
}
return cards;
}
+39
View File
@@ -0,0 +1,39 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make card games |
//| 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>
#include <script/scriptable.hpp>
class Set;
DECLARE_POINTER_TYPE(Card);
// ----------------------------------------------------------------------------- : UpdateCardsScript
/// A script to add one or more cards to a set
class UpdateCardsScript : public IntrusivePtrBase<UpdateCardsScript> {
public:
Version before_version;
String description;
Scriptable<bool> enabled;
OptionalScript script;
bool operator < (const UpdateCardsScript& other) const {
return before_version < other.before_version;
}
/// Perform the script; return the cards (if any)
void perform(Set& set, CardP& card, vector<CardP>& out);
/// Perform the script; add cards to the set
vector<CardP> perform(Set& set, CardP card); // don't use CardP& here, because set.cards may get reallocated which would invalidate the ref
DECLARE_REFLECTION();
};