Page 1 of 9

Card Development - talk about cards code here

PostPosted: 08 Aug 2020, 15:45
by Aswan jaguar
I made Orcish Librarian and mostly works right but it needs certainly some adjustments or changes in the loop ( if this is the correct loop to begin with ).
Below code will fail if cards in deck are less than 8, as it will exile only cards above the last 4 and never the last 4,3,2,1.
So, I really need your help to finish this.
DONE

Code: Select all
int card_orcish_librarian(int player, int card, event_t event){
/* Orcish Librarian   |1|R   0x000000
 * Creature - Orc 1/1
 * |R, |T: Look at the top eight cards of your library. Exile four of them at random, then put the rest on top of your library in any order. */
 
    if( event == EVENT_RESOLVE_ACTIVATION ){
      if( count_deck(player) ){
         int amount = MIN(8, count_deck(player));
            show_deck( player, deck_ptr[player], amount, "Here's the top eight cards of your deck.", 0, 0x7375B0 );
         
          int count_g = amount;
          int cards = 0;
          int rnd_ex = MIN(4, amount);
          while( cards < 4 && count_g > 0){
               int rnd = count_g - (rnd_ex +1);
               if( rnd > cards ){
                  rnd = internal_rand(count_g);
               }
               rfg_card_in_deck(player, rnd);      
               cards++;
               count_g--;
           }
            rearrange_top_x(player, player, amount-rnd_ex);
         }
   }
   return generic_activated_ability(player, card, event, GAA_UNTAPPED, MANACOST_R(1), 0, NULL, NULL);
}

Re: Card Development - talk about cards code here

PostPosted: 09 Aug 2020, 15:35
by FastEddie
I had a quick try with a deck with only 4 cards left. So

amount = 4,
count_g = 4,
rnd_ex = 4.

But then rnd = 4 - (4 + 1) = -1. Same holds when you start with 5 cards or more, you end up with a -1 eventually. In these cases rfg_card_in_deck does nothing.

This code works for 4 (or less) cards. I didn't test 5 to 8, so if you have a test case handy pls give it a try.

Code: Select all
int card_orcish_librarian(int player, int card, event_t event){
/* Orcish Librarian   |1|R   0x000000
 * Creature - Orc 1/1
 * |R, |T: Look at the top eight cards of your library. Exile four of them at random, then put the rest on top of your library in any order. */
 
   if( event == EVENT_RESOLVE_ACTIVATION ){
      if( count_deck(player) ){
         int amount = MIN(8, count_deck(player));
         if (amount == 8 ) {
            show_deck( player, deck_ptr[player], amount, "Here's the top eight cards of your deck.", 0, 0x7375B0 );

            int count_g = amount;
            int cards = 0;
            int rnd_ex = MIN(4, amount);
            while( cards < 4 && count_g > 0){
               int rnd = count_g - (rnd_ex +1);
               if( rnd > cards ){
                  rnd = internal_rand(count_g);
               }
               rfg_card_in_deck(player, rnd);
               cards++;
               count_g--;
            }
            rearrange_top_x(player, player, amount-rnd_ex);
         } else {
            char buffer[100];
            int pos = scnprintf(buffer, 100, "Here's the top ");
            switch (amount) {
               case 1: pos+=scnprintf(buffer+pos, 100-pos, "one card"); break;
               case 2: pos+=scnprintf(buffer+pos, 100-pos, "two cards"); break;
               case 3: pos+=scnprintf(buffer+pos, 100-pos, "three cards"); break;
               case 4: pos+=scnprintf(buffer+pos, 100-pos, "four cards"); break;
               case 5: pos+=scnprintf(buffer+pos, 100-pos, "five cards"); break;
               case 6: pos+=scnprintf(buffer+pos, 100-pos, "six cards"); break;
               case 7: pos+=scnprintf(buffer+pos, 100-pos, "seven cards"); break;
            }
            pos+=scnprintf(buffer+pos, 100-pos, " of your deck.");

            show_deck( player, deck_ptr[player], amount, buffer, 0, 0x7375B0 );
            if (amount <= 4) {
               // Remove remaining deck
               int count;
               for (count = 0; count < amount; count++) {
                  rfg_card_in_deck(player, 0);
               }
            } else {
               int remain = amount - 4;
               int count;
               for (count = 0; count < 4; count++) {
                  rfg_card_in_deck(player, internal_rand(amount));
               amount--;
               }
            rearrange_top_x(player, player, remain);
            }
         }
      }
   }
   return generic_activated_ability(player, card, event, GAA_UNTAPPED, MANACOST_R(1), 0, NULL, NULL);
}

Re: Card Development - talk about cards code here

PostPosted: 09 Aug 2020, 16:04
by Aswan jaguar
Thanks FastEddie, it works as I guess you expected it, to do so. Is anything else that I should have done differently to be more exact with the cards rules text or code wise?

Re: Card Development - talk about cards code here

PostPosted: 09 Aug 2020, 17:32
by FastEddie
No, your code was good, it just didn't take account of this special case. Rule wise I found no errata stating something different, so to me it's ok to exile up to 4 cards and rearrange the rest if less than 8 cards are left. I personally prefer more speaking (some might say talkative ;) ) variable names as it makes it easier for me to read my own code after a while without wondering what exactly the programmer thought. One could probably shorten the whole thing with clever usage of min and max functions but I think this would make it less readable and there is probably no advantage in terms of execution time.

Breath of Dreams - DONE

PostPosted: 06 Oct 2020, 17:52
by Aswan jaguar
I want some help with Breath of Dreams guys, both abilities work on their own but not when both are added. If both are present the abilities get messed up causing crash. DONE
Code: Select all
static void effect_breath_dream(int player, int card, event_t event, kill_t kill_mode)
{
  upkeep_trigger_ability(player, card, event, ANYBODY);

  if (event == EVENT_UPKEEP_TRIGGER_ABILITY)
   {
     int c, p = current_turn;
     char marked[151] = {0};
     for (c = 0; c < active_cards_count[p]; ++c)
      if (in_play(p, c) && is_what(p, c, TYPE_CREATURE))
        marked[c] = 1;

     card_instance_t* inst;
                       test_definition_t this_test;
      default_test_definition(&this_test, TYPE_CREATURE);
      this_test.color = get_sleighted_color_test(player, card, COLOR_TEST_GREEN);
     for (c = 0; c < active_cards_count[p]; ++c)
      if (marked[c]   // was in play and a creature at start of resolution (iterating in reverse isn't sufficient)
         && (inst = in_play(p, c))            // hasn't left play due to a trigger from a previously-unpaid upkeep
         && !(inst->token_status & STATUS_DYING)   // hasn't been marked dying by a trigger from a previously-unpaid upkeep
         && is_what(p, c, TYPE_CREATURE)         // hasn't stopped being a creature due to a trigger from a previously-unpaid upkeep
         && new_make_test_in_play(p, c, -1, &this_test)
         && (!has_mana(p, COLOR_COLORLESS, 1)
            || cumulative_upkeep_arbitrary(player, card, p, c, event, MANACOST_X(1))))
        kill_card(p, c, kill_mode);
   }
}

int card_breath_of_dreams(int player, int card, event_t event){
   /* Breath of Dreams   |2|U|U   0x000000
    * Enchantment
    * Cumulative upkeep |U
    * |S Green creatures have "Cumulative upkeep |1." */

      //cumulative_upkeep(player, card, event, MANACOST_U(1));

   effect_breath_dream(player, card, event, KILL_SACRIFICE);

   return global_enchantment(player, card, event);
}

Balduvian Shaman - DONE

PostPosted: 06 Oct 2020, 17:59
by Aswan jaguar
For Balduvian Shaman I have no idea how to make it check "that doesn't have cumulative upkeep". It needs a new EVENT for this? the rest works.

Code: Select all
static int doesnt_have_cumulative_upkeep(int iid, int me, int player, int card, event_t event){//not checking upkeep - activates
     // ?
   return 0;
}

static int effect_basha(int player, int card, event_t event){

   if( get_card_instance(player, card)->damage_target_player > -1 ){
      card_instance_t *instance = get_card_instance(player, card);
      int p = instance->damage_target_player;
      int c = instance->damage_target_card;

      cumulative_upkeep_arbitrary(player, card, p, c, event, MANACOST_X(1));
   }

   return 0;
}

int card_balduvian_shaman(int player, int card, event_t event){//in progress
   /* Balduvian Shaman   |U   0x000000
    * Creature - Human Cleric Shaman 1/1
    * |T: Change the text of target |Swhite enchantment you control that doesn't have cumulative upkeep by replacing all instances of one color word with another.
      That enchantment gains "Cumulative upkeep |1." */

    target_definition_t td;
   default_target_definition(player, card, &td, TYPE_ENCHANTMENT );
   td.required_color = get_sleighted_color_test(player, card, COLOR_TEST_WHITE);
   td.allowed_controller = td.preferred_controller = player;
   td.extra = (int)doesnt_have_cumulative_upkeep;
   td.special = TARGET_SPECIAL_EXTRA_FUNCTION;

   card_instance_t *instance = get_card_instance( player, card);

   if( event == EVENT_RESOLVE_ACTIVATION ){
      if( valid_target(&td) ){
         replace_all_instances_of_one_color_word_with_another(player, card, instance->targets[0].player, instance->targets[0].card);
         create_targetted_legacy_effect(instance->parent_controller, instance->parent_card, &effect_basha, instance->targets[0].player, instance->targets[0].card);
      }
   }

    return generic_activated_ability(player, card, event, GAA_UNTAPPED | GAA_CAN_TARGET | GAA_LITERAL_PROMPT, MANACOST0, 0, &td,
                                    get_sleighted_color_text(player, card, "Select target %s ench/ment without cumulative upkeep you control.", COLOR_WHITE));
 }

Exploding Borders - DONE

PostPosted: 18 Oct 2020, 13:21
by Aswan jaguar
This is on of the cards coded in the past but never got in release. The issue is I can't find a way to make it count the land that it put onto the battlefield with the first ability to count for it's second ability total damage.
Can we force recalculation of lands in play after domain ability and before calculating basic land types for damage?
DONE
Code: Select all
int card_exploding_borders(int player, int card, event_t event){//UNUSEDCARD
   /* CARD_ID_EXPLODING_BORDERS   10140
   Exploding Borders   |2|R|G
   Sorcery
   Domain - Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle your library.
   ~ deals X damage to target player, where X is the number of basic land types among lands you control. */

   if( ! IS_GS_EVENT(player, card, event) ){
      return 0;
   }

   target_definition_t td1;
   default_target_definition(player, card, &td1, TYPE_CREATURE);
   td1.zone = TARGET_ZONE_PLAYERS;
   td1.allow_cancel = 0;

   card_instance_t *instance = get_card_instance( player, card );

   if(event == EVENT_RESOLVE_SPELL ){
      if( valid_target(&td1) ){
         tutor_basic_land(player, 1, 1);
         int damage = count_domain(player, card);
         if( damage > 0 ){
            damage_player(instance->targets[0].player, damage, player, card);
         }
      }
      kill_card(player, card, KILL_DESTROY);
   }

   return generic_spell(player, card, event, GS_CAN_TARGET, &td1, "TARGET_PLAYER", 1, NULL);
}

Re: Card Development - talk about cards code here

PostPosted: 30 Oct 2020, 07:19
by drool66
For Balduvian Shaman I have no idea how to make it check "that doesn't have cumulative upkeep".
The only way I can think of is the worst way possible - to check by csvid, then to check if there is a card like Mana Chains or Balduvian Shaman's legacy attached to something. Not hard to do - only about 90 cards are involved, but crummy.
I can't find a way to make it count the land that it put onto the battlefield with the first ability
Domain won't work here. It uses basiclandtypes_controlled[], which is only set during recalculate_all_cards_in_play(), so unless you want to run that in the middle of resolution, you can probably just do a regular CYCLE_ALL_CARDS

Re: Card Development - talk about cards code here

PostPosted: 30 Oct 2020, 07:35
by drool66
I want some help with Breath of Dreams guys, both abilities work on their own but not when both are added. If both are present the abilities get messed up causing crash.
More than one way to do this, but I believe you need to consolidate the triggers in some way. What I would do is take "upkeep_trigger_ability" from the breath_of_dreams_effect and keep it set to "anybody", then for the combined "if( event == EVENT_UPKEEP_TRIGGER )" section, copy the code from events.c->cumulative_upkeep_arbitrary(), and set that part off with "if( current_turn == player ){" and then close it off and continue with the rest of the breath_of_dreams_effect section. So it looks like (conceptually):
Code: Select all
  upkeep_trigger_ability(player, card, event, ANYBODY);
  if (event == EVENT_UPKEEP_TRIGGER_ABILITY){
    if( current_turn == player ){
      lines 190-204 of upkeep.c, with proper arguments filled in
    }
    rest of breath_of_dreams effect code
  }
  return global_enchantment()
}

Re: Card Development - talk about cards code here

PostPosted: 31 Oct 2020, 11:14
by Aswan jaguar
Thanks drool66, I have done Breath of Dreams as you suggested and now it works.
For Exploding Borders I went with recalculate_all_cards_in_play(). I tried firstly with your other suggestion to use cycle_all_cards() but I didn't manage to do it that way. However it is a sorcery and it will only check recalculate_all_cards_in_play() while it resolves so not going to be an issue of slowing down game like it does with EVENTs that are checked continually, right?

EDIT: For Balduvian Shaman I tried to import shandalar's EVENT_QUERRY_CUMULATIVE_UPKEEP (commit 4845b11) which didn't seem to be difficult to adopt but proved too much for me.

Re: Card Development - talk about cards code here

PostPosted: 31 Oct 2020, 17:03
by drool66
For Exploding Borders I went with recalculate_all_cards_in_play()
So you probably shouldn't do that but, it did make me realize something. basiclandtypes_controlled[ ] is actually set in count_colors_of_lands_in_play(), which is then called from recalculate_all_cards_in_play(). It would make a lot more sense to run count_colors_of_lands_in_play() instead; in fact, this is probably the "proper" way to do it.

For Balduvian Shaman I tried to import shandalar's EVENT_QUERRY_CUMULATIVE_UPKEEP (commit 4845b11) which didn't seem to be difficult to adopt but proved too much for me.
I think I can pull it off, only because I figured the events system out for Baral et al. I'll put it on my list for the update after this coming one.

Chaos Moon - DONE

PostPosted: 08 Nov 2020, 14:02
by Aswan jaguar
I have made Chaos Moon. It uses 2 effect cards for each of it's mana changing effects and 2 effects for the p/t change on creatures ( through pump_creatures_until_eot() ). It seems to be almost as good as the cards I took code from. One issue that is apparent if you put in play a red or more creatures while Chaos Moon is in play is that the small card on those creatures won't display it's image but a "random" .pic I believe from CardArt file. As steps or couple of turns pass the "random" .pic will change 2,3 times and then it will load it's normal card art.
And I don't have the slightest idea what causes this.
DONE
Code: Select all
int chaos_moon_legacy(int player, int card, event_t event){

   if( event == EVENT_CAST_SPELL && is_what(affected_card_controller, affected_card, TYPE_LAND) &&
       has_subtype(affected_card_controller, affected_card, get_hacked_subtype(player, card, SUBTYPE_MOUNTAIN)) )
      set_special_flags2(affected_card_controller, affected_card, SF2_CONTAMINATION);

   if( event == EVENT_COUNT_MANA ){
      int p;
      for(p=0;p<=1;p++){
         color_t clr;
         for (clr = COLOR_COLORLESS; clr <= COLOR_ARTIFACT; ++clr){
            mana_color_override[p][clr] = COLOR_COLORLESS;
         }
         int c;
         for(c=0; c<active_cards_count[p]; c++){
            if( in_play(p, c) && is_what(p, c, TYPE_LAND) && has_subtype(p, c, get_hacked_subtype(player, card, SUBTYPE_MOUNTAIN)) ){
               set_special_flags2(p, c, SF2_CONTAMINATION);
            }
         }
      }
   }
      
   if( eot_trigger(player, card, event) ){
      kill_card(player, card, KILL_EXILE);
      cant_be_responded_to = 1;
   }
      return 0;
}

int chaos_moon_legacy_odd(int player, int card, event_t event){
   if ((event == EVENT_COUNT_MANA || event == EVENT_TAP_CARD) && is_what(affected_card_controller, affected_card, TYPE_LAND)
      && has_subtype(affected_card_controller, affected_card, get_hacked_subtype(player, card, SUBTYPE_MOUNTAIN))){
      // See comments in card_mana_flare().

      if (!in_play(player, card)){
         return 0;
      }

      if (event == EVENT_COUNT_MANA){
         if (is_tapped(affected_card_controller, affected_card) || is_animated_and_sick(affected_card_controller, affected_card)
            || !can_produce_mana(affected_card_controller, affected_card) ){
            return 0;
         }

         declare_mana_available(affected_card_controller, COLOR_RED, 1);
      } else {   // event == EVENT_TAP_CARD
         if (tapped_for_mana_color >= 0){
            /* Triggers even if the land produced no mana so long as it was tapped for a mana ability (such as a Tolarian Academy that somehow became a
             * forest too, with no artifacts in play), by analogy with the ruling for Overabundance.  Differs from Mana Flare since that "adds one
             * mana... of any type that land produced". */
            produce_mana(affected_card_controller, COLOR_RED, 1);
         }
      }
   }
      
    if( eot_trigger(player, card, event) ){
      kill_card(player, card, KILL_EXILE);
      cant_be_responded_to = 1;
   }
   return 0;
}

int card_chaos_moon(int player, int card, event_t event){
   /* Chaos Moon   |3|R   0x000000
    * Enchantment
    * At the beginning of each upkeep, count the number of permanents. If the number is odd, until end of turn, |Sred creatures get +1/+1
    *       and whenever a player taps |Ha Mountain for mana, that player adds |R to his or her mana pool.
    * If the number is even, until end of turn, |Sred creatures get -1/-1 and if a player taps |Ha Mountain for mana,
    *      that |H2Mountain produces colorless mana instead of any other type. */

   upkeep_trigger_ability(player, card, event, ANYBODY);

   if( event == EVENT_UPKEEP_TRIGGER_ABILITY ){
     int n = count_permanents_by_type(ANYBODY, TYPE_PERMANENT);
     scanf("%d", &n);
    
     test_definition_t this_test;
     default_test_definition(&this_test, TYPE_CREATURE);
     this_test.color = get_sleighted_color_test(player, card, COLOR_TEST_RED);
     this_test.color_flag = MATCH;

     if (n%2 == 0){ // even
        pump_creatures_until_eot(player, card, ANYBODY, 2, -1, -1, 0, 0, &this_test);
        alternate_legacy_text(4, player, create_legacy_effect(player, card, &chaos_moon_legacy));
     }
      else{ // odd
          pump_creatures_until_eot(player, card, ANYBODY, 1, 1, 1, 0, 0, &this_test);
          alternate_legacy_text(3, player, create_legacy_effect(player, card, &chaos_moon_legacy_odd));
      }
   }

   return global_enchantment(player, card, event);
}

Re: Card Development - talk about cards code here

PostPosted: 08 Nov 2020, 16:33
by drool66
First of all, awesome idea for a card to code, and I appreciate that you're using the new mana variables.
Second, I tried your code & I do not have the same issue - the card works great. Could be a packaging issue with the patch - are you using v1.1?

Re: Card Development - talk about cards code here

PostPosted: 09 Nov 2020, 09:54
by FastEddie
Nice one!

Just one comment from my side. The scanf here
Code: Select all
int n = count_permanents_by_type(ANYBODY, TYPE_PERMANENT);
scanf("%d", &n);
looks fishy and is dangerous. It reads a number the user has to enter via keyboard and would overwrite n which you just calculated. If you are running Magic.exe from a command prompt you should see something, otherwise it probably gets ignored but it is risky nevertheless.

Re: Card Development - talk about cards code here

PostPosted: 09 Nov 2020, 16:22
by Aswan jaguar
It was
drool66 wrote:Second, I tried your code & I do not have the same issue - the card works great. Could be a packaging issue with the patch - are you using v1.1?
Yes, it was a packaging issue. And removed the scanf("%d", &n); code. Thanks, guys.