Page 8 of 9

Re: Card Development - talk about cards code here

PostPosted: 03 Apr 2023, 15:32
by Aswan jaguar
I tried to do Complicate and add it's clause "unless its controller pays |1." to counterspell_cycling() but I couldn't make the prompt to charge cost to appear. Below only the cards code, which works apart from this clause.
Code: Select all
int counterspell_cycling(int player, int card, event_t event, int colorless, int black, int blue, int green, int red, int white);
int card_complicate(int player, int card, event_t event){
   /* CARD_ID_COMPLICATE   6091 // remain in progress counters the card without clause "unless its controller pays |1."
   Complicate   |2|U
   Instant
   Counter target spell unless its controller pays |3.
   Cycling |2|U (|2|U, Discard this card: Draw a card.)
   When you cycle ~, you may counter target spell unless its controller pays |1. */

   if( IS_ACTIVATING_FROM_HAND(event) ){
      return counterspell_cycling(player, card, event, MANACOST_XU(2, 1));
   }

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

   if( event == EVENT_RESOLVE_SPELL ){
      counterspell_resolve_unless_pay_x(player, card, NULL, 0, 3);
      kill_card(player, card, KILL_DESTROY);
   }

   return generic_spell(player, card, event, GS_COUNTERSPELL, NULL, NULL, 0, NULL);
}

Re: Card Development - talk about cards code here

PostPosted: 05 Apr 2023, 14:34
by drool66
You'll have to inline counterspell_cycling() to a new "counterspell_cycling_resolve_unless_pay_x()" or something like that, and have it call counterspell_resolve_unless_pay_x() instead of real_counter_a_spell() on EVENT_RESOLVE_ACTIVATION_FROM_HAND (counterspell_resolve_unless_pay_x() validates on its own, so no need to do it twice)
Also, counterspell_cycling should check PB_HAND_ABILITIES_DISABLED not PB_GY_ABILITIES_DISABLED (probably my mistake)

[fixed]Endemic Plague how to get the type of sacrificed type

PostPosted: 21 Apr 2023, 13:06
by Aswan jaguar
Endemic Plague, I have no idea how to get the type of the sacrificed creature then save it to use it when spell resolves.
| Open
Code: Select all
int card_endemic_plague(int player, int card, event_t event){ // in progress how get sacrificed creature type
   /* CARD_ID_ENDEMIC_PLAGUE   6132
   Endemic Plague   |3|B
   Sorcery
   As an additional cost to cast this spell, sacrifice a creature.
   Destroy all creatures that share a creature type with the sacrificed creature. They can't be regenerated. */

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

   card_instance_t *instance = get_card_instance(player, card);

   if( event == EVENT_CAN_CAST ){
      return generic_spell(player, card, event, GS_SAC_CREATURE_AS_COST, NULL, NULL, 1, NULL);
   }

   test_definition_t test;
   new_default_test_definition(&test, TYPE_CREATURE, "Select a creature to sacrifice.");

   if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
      int sac = new_sacrifice(player, card, player, SAC_JUST_MARK|SAC_AS_COST|SAC_RETURN_CHOICE, &test);
      if (!sac){
         cancel = 1;
         return 0;
      }

                           
      kill_card(BYTE2(sac), BYTE3(sac), KILL_SACRIFICE);
   }

   if( event == EVENT_RESOLVE_SPELL ){
      int quantity = 0;
      int i;
      int p;
      for(p=0;p<2;p++){
         for(i=0; i<active_cards_count[p]; i++){
            if( in_play(p, i) && is_what(p, i, TYPE_CREATURE) && shares_creature_subtype(p, instance->targets[1].player, p, i) )
            {
               //shares_creature_subtype(player, instance->targets[1].player, p, i);
               quantity++;
            }
         }
      }
      kill_card(player, card, KILL_DESTROY);
   }

   return 0;
}

Re: Card Development - talk about cards code here

PostPosted: 21 Apr 2023, 16:24
by drool66
The problem isn't really getting the subtypes, it's storing them betwen EVENT_CAST_SPELL and EVENT_RESOLVE_SPELL. You can get the subtypes in EVENT_CAST_SPELL with:
Code: Select all
     reported_subtypes_t my_stypes = {{0}};
     get_creature_subtypes(BYTE2(sac), BYTE3(sac), &my_stypes);
The issue is reported_subtypes_t has a length of 26 int, though I'm pretty sure we can truncate each subtype_t to 16 bits. We might want to change the definition of reported_subtypes_t to reflect this. In any case, the only place I know of to store all that is in the targets array :roll: Storing them at the end of the targets array is probably safest and hopefully won't interfere with anything that checks targets. Each target_t, being 2 int, can store 4 int16_t, so counting backwards from targets[18] we can start at targets[12].card. We end up with something like:
Code: Select all
#define NUM_SUBTYPES 26
typedef struct
{
   uint16_t reported_subtypes16[NUM_SUBTYPES];
} reported_subtypes16_t;

static void store_subtypes_in_targets_array(card_instance_t* inst, reported_subtypes_t *my_subtypes){
   reported_subtypes16_t* stored_subtypes = (reported_subtypes16_t*)(&inst->targets[12].card);
   int q;
   for(q=0; q<NUM_SUBTYPES; q++){
      stored_subtypes->reported_subtypes16[q] = (my_subtypes->reported_subtypes[q] & 0xFFFF);
   }
}

static void retrieve_subtypes_from_targets_array(card_instance_t* inst, reported_subtypes_t *my_subtypes){
   reported_subtypes16_t* stored_subtypes = (reported_subtypes16_t*)(&inst->targets[12].card);
   int q;
   for(q=0; q<NUM_SUBTYPES; q++){
      my_subtypes->reported_subtypes[q] = stored_subtypes->reported_subtypes16[q];
   }
}

int card_endemic_plague(int player, int card, event_t event){
   /* CARD_ID_ENDEMIC_PLAGUE   6132
   Endemic Plague   |3|B
   Sorcery
   As an additional cost to cast this spell, sacrifice a creature.
   Destroy all creatures that share a creature type with the sacrificed creature. They can't be regenerated. */

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

   if( event == EVENT_CAN_CAST ){
      return generic_spell(player, card, event, GS_SAC_CREATURE_AS_COST, NULL, NULL, 1, NULL);
   }

   test_definition_t test;
   new_default_test_definition(&test, TYPE_CREATURE, "Select a creature to sacrifice.");

   if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
      int sac = new_sacrifice(player, card, player, SAC_JUST_MARK|SAC_AS_COST|SAC_RETURN_CHOICE, &test);
      if (!sac){
         cancel = 1;
         return 0;
      }

     reported_subtypes_t my_stypes = {{0}};
     get_creature_subtypes(BYTE2(sac), BYTE3(sac), &my_stypes);
     DECL_INST();
     store_subtypes_in_targets_array(inst, &my_stypes);
      kill_card(BYTE2(sac), BYTE3(sac), KILL_SACRIFICE);
   }

   if( event == EVENT_RESOLVE_SPELL ){
     reported_subtypes_t my_stypes = {{0}};
     DECL_INST();
     retrieve_subtypes_from_targets_array(inst, &my_stypes);
      int i, p, q;
      for(p=0;p<2;p++){
         for(i=0; i<active_cards_count[p]; i++){
            if( in_play(p, i) && is_what(p, i, TYPE_CREATURE) )
            {
            for(q=0; q<NUM_SUBTYPES; q++){
               if( is_creature_type(my_stypes.reported_subtypes[q]) && has_subtype(p, i, my_stypes.reported_subtypes[q]) )
                  kill_card(p, i, KILL_BURY);
            }
            }
         }
      }
      kill_card(player, card, KILL_DESTROY);
   }
   return 0;
}
#undef NUM_SUBTYPES
The has_subtype() line might not be exactly right for some edge cases. You might want to scan through shares_creature_subtype() for those.

Extra Arms targetting done by aura instead of creature

PostPosted: 29 May 2023, 12:39
by Aswan jaguar
Extra Arms the targeting is done by the aura instead of the creature. If I change the target in default_target_definition() to be the aura creature then targeting works fine but the damage on resolution is done to enchanted creature instead of the targeted creature.
Code: Select all
int card_extra_arms(int player, int card, event_t event){
   /* CARD_ID_EXTRA_ARMS   6524
   Extra Arms   |4|R
   Enchantment - Aura
   Enchant creature
   Whenever enchanted creature attacks, it deals 2 damage to any target. */

   if( event == EVENT_DECLARE_ATTACKERS ){
      card_instance_t* inst;
      if( (inst = in_play(player, card)) && inst->damage_target_player > -1 )
         store_attackers(player, card, event, DAT_TRACK, inst->damage_target_player, inst->damage_target_card, NULL);
   }
   dat_ability(player, card, event, DAT_RESOLVE_TRIGGER_MANDATORY, 1);
   if( event == EVENT_DAT_ABILITY ){
      DECL_INST();
      target_definition_t td;
      //default_target_definition(inst->damage_target_player, inst->damage_target_card, &td, TYPE_CREATURE|TYPE_PLANESWALKER);
      // targetting done by aura wrongly but otherwise as above enchanted creature damages itself instead of target
      default_target_definition(player, card, &td, TYPE_CREATURE|TYPE_PLANESWALKER);
      td.zone = TARGET_ZONE_CREATURE_OR_PLAYER;
      td.allow_cancel = 0;

      if( can_target(&td) && new_pick_target(&td, "TARGET_ANY", 0, 0) ){
         damage_creature(inst->targets[0].player, inst->targets[0].card, 2, inst->damage_target_player, inst->damage_target_card);
      }
      inst->number_of_targets = 0;
   }

   return aura_pref_controller(player, card, event, player, NULL);
}

Re: Card Development - talk about cards code here

PostPosted: 30 May 2023, 05:34
by drool66
Code: Select all
int card_extra_arms(int player, int card, event_t event){
   /* CARD_ID_EXTRA_ARMS   6524
   Extra Arms   |4|R
   Enchantment - Aura
   Enchant creature
   Whenever enchanted creature attacks, it deals 2 damage to any target. */

   if( event == EVENT_DECLARE_ATTACKERS ){
      card_instance_t* inst;
      if( (inst = in_play(player, card)) && inst->damage_target_player > -1 )
         store_attackers(player, card, event, DAT_0, inst->damage_target_player, inst->damage_target_card, NULL);
   }
   dat_ability(player, card, event, DAT_RESOLVE_TRIGGER_MANDATORY, 1);
   if( event == EVENT_DAT_ABILITY ){
      DECL_INST();
      DECL_ATT();
      int ret_location = att->number_of_targets;
      target_t old_targ = att->targets[ret_location];
      target_definition_t td;
      default_target_definition(inst->damage_target_player, inst->damage_target_card, &td, TYPE_CREATURE|TYPE_PLANESWALKER);
      td.zone = TARGET_ZONE_CREATURE_OR_PLAYER;
      td.allow_cancel = 0;

      if( can_target(&td) && new_pick_target(&td, "TARGET_ANY", -1, 0) ){
         damage_creature(att->targets[ret_location].player, att->targets[ret_location].card, 2, inst->damage_target_player, inst->damage_target_card);
      }
       att->targets[ret_location] = old_targ;
      att->number_of_targets = ret_location;
   }

   return aura_pref_controller(player, card, event, player, NULL);
}
Because we're not yet doing trigger cards we have to store the target on the attached instance, but because it's done in one step we can just back up and restore. I would add DAT_0 in manalink.h instead of just putting 0 in the mode of store_attackers().

[fixed]Liar's Pendulum

PostPosted: 16 Jul 2023, 11:19
by Aswan jaguar
I tried several things but I still have two issues with Liar's Pendulum: AI's choice if card is in hand or not, is not random, I tried internal_rand() and the code I am having here but the outcome is always 0; overlapping code issue? And second issue is that the outcome when choosing to draw a card and the guess is wrong, so you should draw a card, I estimate is correct about 70% of the times.
Code: Select all
int card_liars_pendulum(int player, int card, event_t event){
   /* CARD_ID_LIARS_PENDULUM   6703 // in progress to add
   Liar's Pendulum   |1
   Artifact
   |2, |T: Choose a card name. Target opponent guesses whether a card with that name is in your hand. You may reveal your hand.
    If you do and your opponent guessed wrong, draw a card. */

    if( ! IS_GAA_EVENT(event) ){
      return 0;
   }

   target_definition_t td;
   default_target_definition(player, card, &td, 0);
   td.zone = TARGET_ZONE_PLAYERS;

   card_instance_t *instance = get_card_instance(player, card);

   if( event == EVENT_RESOLVE_ACTIVATION ){
      if( valid_target(&td) && hand_count[instance->targets[0].player] > 0 ){
         int t_player = instance->targets[0].player;
         int csvid = -1;
         if( IS_AI(player) ){
            csvid = ai_name_a_random_csvid(player, card, player, TYPE_ANY, NULL);
         }
         else{
            test_definition_t test;
            new_default_test_definition(&test, TYPE_ANY, "Choose a card name.");
            csvid = new_card_from_list(player, 3, &test);
         }
         test_definition_t test2;
         default_test_definition(&test2, TYPE_ANY);
         test2.id = csvid;
         test2.zone = TARGET_ZONE_HAND;
         int ai_ch = rand() % 2;
         instance->targets[1].player = do_dialog(t_player, t_player, card, -1, -1, " Named card in hand\nNamed card not in hand", ai_ch);// internal_rand doesn't seem to work
         if( do_dialog(player, player, card, -1, -1, " Reveal/Draw a card\n Pass", 0) == 0 ){
            reveal_target_player_hand(player);
            if( (instance->targets[1].player == 1 && check_battlefield_for_special_card(player, card, player, CBFSC_CHECK_ONLY, &test2)) ||
               (instance->targets[1].player == 0 && !check_battlefield_for_special_card(player, card, player, CBFSC_CHECK_ONLY, &test2)) )
            {
               draw_a_card(player);
            }
         }
      }
   }

   return generic_activated_ability(player, card, event, GAA_UNTAPPED | GAA_CAN_ONLY_TARGET_OPPONENT, MANACOST_X(2), 0, &td, "TARGET_PLAYER");
}

Re: Card Development - talk about cards code here

PostPosted: 19 Jul 2023, 07:47
by drool66
I don't know why that wouldn't work, but here's what I've got, and it seems to work alright:
Code: Select all
int card_liars_pendulum(int player, int card, event_t event){
   /* CARD_ID_LIARS_PENDULUM   6703 // in progress to add
   Liar's Pendulum   |1
   Artifact
   |2, |T: Choose a card name. Target opponent guesses whether a card with that name is in your hand. You may reveal your hand.
    If you do and your opponent guessed wrong, draw a card. */

    if( ! IS_GAA_EVENT(event) ){
      return 0;
   }

   target_definition_t td;
   default_target_definition(player, card, &td, 0);
   td.zone = TARGET_ZONE_PLAYERS;

   card_instance_t *instance = get_card_instance(player, card);

   if( event == EVENT_RESOLVE_ACTIVATION ){
      if( valid_target(&td) && hand_count[player] > 0 ){
         int t_player = instance->targets[0].player;
         int csvid = -1;
         test_definition_t test;
         new_default_test_definition(&test, TYPE_ANY, "Choose a card name.");
         if( IS_AI(player) ){
            csvid = ai_name_a_random_csvid(player, card, player, TYPE_ANY, NULL);
         }
         else{
            csvid = new_card_from_list(player, 3, &test);
         }
         test.id = csvid;
         test.zone = TARGET_ZONE_HAND;
       int card_in_hand = check_battlefield_for_special_card(player, card, player, CBFSC_CHECK_ONLY, &test);

      card_ptr_t* c = cards_ptr[csvid];
      char buffer1[100];
      scnprintf(buffer1, 100, " %s in hand.", c->name);
      char buffer2[100];
      scnprintf(buffer2, 100, " %s not in hand.", c->name);

      int ai_option_1 = (player_bits[player] & PB_HAND_REVEALED) ? card_in_hand ? 1000 : -1000 : 1;
      int ai_option_2 = (player_bits[player] & PB_HAND_REVEALED) ? card_in_hand ? -1000 : 1000 : 1;

       int guess = DIALOG(player, card, event, DLG_WHO_CHOOSES(t_player), DLG_NO_STORAGE, DLG_RANDOM, DLG_NO_CANCEL, DLG_FULLCARD_CSVID(csvid), DLG_HEADER("What's your guess?"),
       buffer1, 1, ai_option_1,
       buffer2, 1, ai_option_2);

       int guess_correct = (guess == 1 && card_in_hand) || (guess == 2 && !card_in_hand);
         if( do_dialog(player, player, card, -1, -1, " Reveal Hand\n Pass", guess_correct) == 0 )
       {
            reveal_target_player_hand(player);
            if( !guess_correct )
            {
               draw_a_card(player);
            }
         }
      }
   }

   return generic_activated_ability(player, card, event, GAA_UNTAPPED | GAA_CAN_ONLY_TARGET_OPPONENT, MANACOST_X(2), 0, &td, "TARGET_PLAYER");
}

Re: Card Development - talk about cards code here

PostPosted: 19 Jul 2023, 12:13
by Aswan jaguar
It seems to work fine. =D>
However, I don't get the code about ai_opinion is this supposed to cheat for AI since you check card_in_hand? (thankfully AI doesn't guess always correctly).
Code: Select all
      int ai_option_1 = (player_bits[player] & PB_HAND_REVEALED) ? card_in_hand ? 1000 : -1000 : 1;
      int ai_option_2 = (player_bits[player] & PB_HAND_REVEALED) ? card_in_hand ? -1000 : 1000 : 1;

Re: Card Development - talk about cards code here

PostPosted: 19 Jul 2023, 13:28
by drool66
If the AI can see your hand, as with Telepathy, it will get the answer right :D

Re: Card Development - talk about cards code here

PostPosted: 19 Jul 2023, 14:33
by Aswan jaguar
drool66 wrote:If the AI can see your hand, as with Telepathy, it will get the answer right :D
It would be a nice touch if it worked. AI still gets it wrong at times while he has Telepathy, or Revelation is in play, which shouldn't be happening with your code.

Re: Card Development - talk about cards code here

PostPosted: 26 Jul 2023, 04:25
by drool66
I dunno; I guess even sometimes AI will pick -1000 over +1000 modifier. If you want it to always be right in that case maybe try removing DLG_RANDOM, or check (player_bits[player] & PB_HAND_REVEALED) && IS_AI(instance->targets[0].player) to manage the legality instead of the AI weight

Re: Card Development - talk about cards code here

PostPosted: 27 Jul 2023, 14:32
by Aswan jaguar
It was DLG_RANDOM preventing it to work right, now AI 100% correct when it has Telepathy in play.
This makes me think that we misuse DLG_RANDOM quite often. I still kept it when Telepathy kind of cards are not in play, so AI decision then is random.

Synod Sanctum parameters error

PostPosted: 08 Aug 2023, 13:14
by Aswan jaguar
Synod Sanctum usually when using the first ability it produces a parameters error. Even when it produces the error it seems to work fine as the targeted card gets exiled and when you use the second ability the correct cards return to the battlefield.
Code: Select all
int card_synod_sanctum(int player, int card, event_t event){
   /* CARD_ID_SYNOD_SANCTUM   6797 // in progress
   [img]Synod Sanctum[/img]   |1
   Artifact
   |2, |T: Exile target permanent you control.
   |2, Sacrifice ~: Return all cards exiled with ~ to the battlefield under your control. */

   if (!IS_GAA_EVENT(event))
   return 0;

   int cuaa = can_use_activated_abilities(player, card);
   target_definition_t td;
   default_target_definition(player, card, &td, TYPE_PERMANENT);
   td.allowed_controller = td.preferred_controller = player;

   enum
   {
   CHOICE_EXILE = 1,
   CHOICE_SACRIFICE
   } choice = DIALOG(player, card, event, DLG_RANDOM,
               "Exile target permanent", cuaa && can_target(&td), 1, DLG_TAP, DLG_MANA(MANACOST_X(2)),
               "Sacrifice", cuaa && can_sacrifice_this_as_cost(player, card), 1, DLG_MANA(MANACOST_X(2)));

   if (event == EVENT_CAN_ACTIVATE)
   return choice;

   //card_instance_t* instance = get_card_instance(player, card);

   if (event == EVENT_ACTIVATE){
      card_instance_t* instance = get_card_instance(player, card);
      switch (choice)
     {
      case CHOICE_EXILE:
        if (player == AI && current_turn == 1-player && current_phase == PHASE_DISCARD)
         ai_modifier += 48;
         instance->number_of_targets = 0;
        new_pick_target(&td, "TARGET_PERMANENT_YOU_CONTROL", 0, 0);
        break;

      case CHOICE_SACRIFICE:
       /* ;int ai_mod = 0;
        if (instance->targets[0].card != -1)
         {
           ai_mod -= 12 * BYTE0(instance->targets[0].card);
           ai_mod += 12 * BYTE1(instance->targets[0].card);
         }
        ai_modifier += (player == AI) ? ai_mod : -ai_mod;*/
        instance->targets[1].card = exiledby_detach(player, card);
        kill_card(player, card, KILL_SACRIFICE);
        break;
     }
   }

   if (event == EVENT_RESOLVE_ACTIVATION){
      card_instance_t* instance = get_card_instance(player, card);
   switch (choice)
     {
      case CHOICE_EXILE:
         if( ! is_token(instance->targets[0].player, instance->targets[0].card) ){
            int leg, idx;
            int iid = get_original_internal_card_id(instance->targets[0].player, instance->targets[0].card);
            exiledby_remember(player, instance->parent_card, player, iid, &leg, &idx);
            kill_card(instance->targets[0].player, instance->targets[0].card, KILL_EXILE);
         }
         else{
            kill_card(instance->targets[0].player, instance->targets[0].card, KILL_EXILE);
         }
         // Keep count of cards exiled by each player, for ai modifier
         /*  if (instance->targets[0].card == -1)
            instance->targets[0].card = 0;

           if (player == 0)
            ++SET_BYTE0(instance->targets[0].card);
           else
            ++SET_BYTE1(instance->targets[0].card); */
        break;

      case CHOICE_SACRIFICE:
           get_back_cards_stored_in_exiledby(player, player-2, instance->targets[1].card, GBC_PUT_PERMANENTS_INTO_BF|GBC_PLAYER_GET_ALL_CARDS);
        break;
     }
   }

   return 0;
}

Re: Card Development - talk about cards code here

PostPosted: 09 Aug 2023, 02:26
by drool66
Just need to initialize the leg & idx variables:

int leg = 0, idx = 0;