Page 3 of 9

Re: Card Development - talk about cards code here

PostPosted: 14 Dec 2020, 06:06
by Korath
You're overcomplicating things. draw_cards_exe(player, amount) just calls draw_a_card(player) amount times in a for loop; and draw_a_card() returns the index of the drawn card. That might be -1 if the draw is prevented early, or might not be in_hand() if the draw is replaced, so you have to check for both.

I'd say to look at Sindbad for an example of usage, except it's been broken to check the highest-index card that was drawn this turn. That's not reliably the most-recently-drawn card (though it usually is), and it'll be plainly incorrect if the draw is replaced or it triggers another draw.

Re: Card Development - talk about cards code here

PostPosted: 15 Dec 2020, 01:46
by drool66
Thank you, Korath. Found the loop at 0x41C190

Aswan jaguar, this is where I ended up with it:

Code: Select all
int card_krovikan_sorcerer(int player, int card, event_t event){
   /* Krovikan Sorcerer   |2|U
    * Creature - Human Wizard 1/1
    * |T, Discard a non|Sblack card: Draw a card.
    * |T, Discard a |Sblack card: Draw two cards, then discard one of them. */

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

    if( event == EVENT_DRAW_PHASE ){
       if(current_turn == player){
          get_card_instance(player, card)->info_slot = 3;
       }
    }

   card_instance_t* instance = get_card_instance(player, card);

   enum{
        CHOICE_DISCARD_NON_BLACK = 1,
        CHOICE_DISCARD_BLACK
   };

   if( event == EVENT_CAN_ACTIVATE ){
       return generic_activated_ability(player, card, event, GAA_UNTAPPED | GAA_DISCARD, MANACOST0, 0, NULL, NULL);
   }

   if( event == EVENT_ACTIVATE ){

      test_definition_t this_test;
      new_default_test_definition(&this_test, TYPE_ANY, get_sleighted_color_text(player, card, "Select a non %s ", COLOR_BLACK));
      this_test.color = get_sleighted_color_test(player, card, COLOR_TEST_BLACK);
      this_test.color_flag = DOESNT_MATCH;
      this_test.zone = TARGET_ZONE_HAND;

      test_definition_t this_test2;
      new_default_test_definition(&this_test2, TYPE_ANY, get_sleighted_color_text(player, card, "Select a %s ", COLOR_BLACK));
      this_test2.color = get_sleighted_color_test(player, card, COLOR_TEST_BLACK);
      this_test2.color_flag = MATCH;
      this_test2.zone = TARGET_ZONE_HAND;

      int dis_non_black = check_battlefield_for_special_card(player, card, player, 0, &this_test);
      int dis_black = check_battlefield_for_special_card(player, card, player, 0, &this_test2);
      int choice = DIALOG(player, card, event, DLG_RANDOM,
                     "Discard a non-black card", dis_non_black, 5,
                     "Discard a black card", dis_black, 10
                );

      if( ! choice ){
         spell_fizzled = 1;
         return 0;
      }
      instance->info_slot = choice;

      if( choice == CHOICE_DISCARD_NON_BLACK){
         if( generic_activated_ability(player, card, event, GAA_UNTAPPED, MANACOST0, 0, NULL, NULL) ){
            int result = new_select_a_card(player, player, TUTOR_FROM_HAND, 0, AI_MIN_VALUE, -1, &this_test);
            if( result != -1){
               discard_card(player, result);
            }
            else{
               untap_card_no_event(player, card);
               spell_fizzled = 1;
            }
         }
      }
      if( choice == CHOICE_DISCARD_BLACK){
         if( generic_activated_ability(player, card, event, GAA_UNTAPPED, MANACOST0, 0, NULL, NULL) ){
            int result2 = new_select_a_card(player, player, TUTOR_FROM_HAND, 0, AI_MIN_VALUE, -1, &this_test2);
            if( result2 != -1){
               discard_card(player, result2);
            }
            else{
               untap_card_no_event(player, card);
               spell_fizzled = 1;
            }
         }
      }
   }

   if( event == EVENT_RESOLVE_ACTIVATION ){
      if( instance->info_slot == CHOICE_DISCARD_NON_BLACK){
         draw_cards(player, 1);
      }
      if( instance->info_slot == CHOICE_DISCARD_BLACK){
         int i;
         int dtt[2][100];
         memset(dtt, 0, sizeof dtt);
         int dtt_count = -1;
         for(i=0;i<2;i++){
            int card_added = draw_a_card(player);
            if( card_added > -1 ){
               if( in_hand(player, card_added) ){
                  if( dtt_count < 0 )
                     dtt_count = 0;
                  dtt[0][dtt_count] = get_card_instance(player, card_added)->internal_card_id;
                  dtt[1][dtt_count] = card_added;
                  dtt_count++;
               }
            }
         }

         test_definition_t this_test;
         new_default_test_definition(&this_test, TYPE_ANY, "Select a card to discard.");

         while( dtt_count > -1){
               int selected = select_card_from_zone(player, player, dtt[0], dtt_count, 1, AI_MIN_VALUE, -1, &this_test);
               if( selected != -1 ){
                  discard_card(player, dtt[1][selected]);
               }
               dtt_count--;
               break;
         }
      }
   }

   return 0;
}

Re: Card Development - talk about cards code here

PostPosted: 15 Dec 2020, 15:11
by Aswan jaguar
Thanks, for the help guys. It works fine now and respects draw replacement.
Korath wrote:or it triggers another draw.
I didn't find how to test this.
I confirm that Sindbad doesn't work correctly with draw replacement.

Also drool66 I saw you used untap_card_no_event() if the ability fizzles. Is it safe to use it to any activated ability when it fizzles? I thought this was only for fake activations for cards that produce mana and have other non mana tapped abilities so they can't also tap for mana when charging mana.

Re: Card Development - talk about cards code here

PostPosted: 15 Dec 2020, 17:55
by drool66
Korath wrote:
or it triggers another draw.
I didn't find how to test this.
Right - I failed to test this. Maybe try giving both players a Mind's Eye or Consecrated Sphinx?
Speaking of this, I think the proper interaction with Thought Reflection would be that you draw four and discard one of those four cards since it's a replacement effect, not a draw trigger, right?
Also drool66 I saw you used untap_card_no_event() if the ability fizzles. Is it safe to use it to any activated ability when it fizzles? I thought this was only for fake activations for cards that produce mana and have other non mana tapped abilities so they can't also tap for mana when charging mana.
I'm not 100% sure. I used it because canceling, including selecting a black card with the first ability and vice-versa, was keeping Krovikan Sorcerer tapped. We want to just remove the state & return to pre-activation without dispatching EVENT_UNTAP_CARD, which I think is what this function is for. Again, not 100% sure, but I would say there's a remote possibility that we could rightly put it in granted_generic_activated_ability() under "failure" - we'd have to consider a lot of scenarios to do that.
I wasn't sure the reasoning for setting info_slot to 3 during the draw phase - that might be depreciated now, I don't know.

Re: Card Development - talk about cards code here

PostPosted: 15 Dec 2020, 18:08
by Aswan jaguar
drool66 wrote:I wasn't sure the reasoning for setting info_slot to 3 during the draw phase - that might be depreciated now, I don't know.
I already removed that - it was one of my foolish ideas and efforts to fix the original issue I posted the card for, which I forgot to remove before attaching the code here.
drool66 wrote:I think the proper interaction with Thought Reflection would be that you draw four and discard one of those four cards since it's a replacement effect, not a draw trigger, right?
This sounds correct. I tested with another card and that worked fine. I will test with Thought Reflection and see what happens.

EDIT: I tested with Mind's Eye and this works fine you get the correct cards.
I looked at the rules for the replacement effect which means that the original effect was replaced so Krovikan Sorcerer didn't draw any card so none will be discarded this is how it currently works so this also seems to work fine.

614.1 Some continuous effects are replacement effects. Like prevention effects (see rule 615), replacement effects apply continuously as events happen—they aren’t locked in ahead of time. Such effects watch for a particular event that would happen and completely or partially replace that event with a different event. They act like “shields” around whatever they’re affecting.

614.1a Effects that use the word “instead” are replacement effects. Most replacement effects use the word “instead” to indicate what events will be replaced with other events.

Re: Card Development - talk about cards code here

PostPosted: 16 Dec 2020, 04:20
by Korath
There's a more specific rule that says it explicitly:

121.6c Some effects perform additional actions on a card after it's drawn. If the draw is replaced, the additional action is not performed on any cards that are drawn as a result of that replacement effect or any subsequent replacement effects.

Re: Card Development - talk about cards code here

PostPosted: 18 Dec 2020, 20:17
by drool66
Here's my attempt at using create_a_card_type() on token creation:

Code: Select all
static int resolve_iid(iid_t int_id, token_generation_t* token){
   if( is_reserved_id(cards_data[int_id].id) )
      goto out;

   iid_t new_type, start_of_dynamic_types = EXE_TYP(iid_t, 0x73c00c);
   iid_t end_of_dynamic_types = EXE_TYP(iid_t, 0x73c05c);   //16 is NUMBER_OF_DYNAMIC_CARD_TYPES, ++16*5

   for( new_type = start_of_dynamic_types; new_type < end_of_dynamic_types; new_type++ ){
      if( cards_data[new_type].id == cards_data[int_id].id &&
         !( memcmp(&cards_data[new_type], &cards_data[int_id], sizeof(card_data_t)))   // An identical version already exists
      ){
         int_id = new_type;
         goto out;
      }
   }

   if( (token->pow < 0 && token->pow != cards_data[int_id].power) && (token->tou < 0 && token->tou != cards_data[int_id].toughness)&& !token->key_plus && !token->color_forced &&
         !(!(cards_data[int_id].type & TYPE_ARTIFACT) && (token->action & TOKEN_ACTION_CONVERT_INTO_ARTIFACT)) &&
         !(token->action & TOKEN_ACTION_ADD_SUBTYPE) )   //nothing that would be added by create_a_card_type() anyway*/
      goto out;

   new_type = create_a_card_type(int_id);
   memcpy(&cards_data[new_type], &cards_data[int_id], sizeof(card_data_t));

   if( token->pow > -1 && token->pow != cards_data[int_id].power){
      cards_data[new_type].power = token->pow;
   }
   if( token->tou > -1 && token->tou != cards_data[int_id].toughness){
      cards_data[new_type].toughness = token->tou;
   }
   if( token->key_plus > 0 ){
      cards_data[new_type].static_ability |= token->key_plus;
   }
   if(!(cards_data[int_id].type & TYPE_ARTIFACT) && (token->action & TOKEN_ACTION_CONVERT_INTO_ARTIFACT)){// not is_what() - deliberately ignore SF2_MYCOSYNTH_LATTICE
      cards_data[new_type].type |= TYPE_ARTIFACT;
   }
   if (token->action & TOKEN_ACTION_ADD_SUBTYPE){
      cards_data[new_type].subtype |= token->action_argument;
   }
   if( token->color_forced ){
      color_test_t forced = token->color_forced;
      int sleight = token->s_player >= 0 && token->s_card >= 0 && !token->no_sleight && !(forced > 0 && (forced & 0x80));
      if (forced <= 0){
         forced = cards_data[int_id].color;
      }
      if (sleight){
         forced = get_sleighted_color_test(token->s_player, token->s_card, forced & COLOR_TEST_ANY_COLORED);
      } else {
         forced &= COLOR_TEST_ANY_COLORED;
      }

      cards_data[new_type].color = forced;
   }

   int_id = new_type;

   out:

   return int_id;
}
I'm calling it from generate_token() but not generate_token_by_id_impl() because that would be unnecessary since those generate the token exactly as described in csv, and not real_generate_token() since that just takes the iid from either of the above. It works great vis a vis all the problems we've been having with tokens, and I've been able to remove many of the awful hacks we've used. I've found two issues - one is the big cards don't show the proper P/T if it's been altered. The other is more or less critical and contained in the code comparing new tokens to the new types that have already been generated - repeated here:
Code: Select all
   iid_t new_type, start_of_dynamic_types = EXE_TYP(iid_t, 0x73c00c);
   iid_t end_of_dynamic_types = EXE_TYP(iid_t, 0x73c05c);   //16 is NUMBER_OF_DYNAMIC_CARD_TYPES, ++16*5

   for( new_type = start_of_dynamic_types; new_type < end_of_dynamic_types; new_type++ ){
      if( cards_data[new_type].id == cards_data[int_id].id &&
         !( memcmp(&cards_data[new_type], &cards_data[int_id], sizeof(card_data_t)))   // An identical version already exists
      ){
         int_id = new_type;
         goto out;
      }
   }
The "if" never evaluates true for either condition and I'm pretty sure it has to do with how I've handled checking the memory addresses. If anyone has any insight, I'd appreciate it, thanks.

Re: Card Development - talk about cards code here

PostPosted: 19 Dec 2020, 03:45
by Korath
0x73c00c holds the iid of the first dynamically-created card_data_t in Shandalar, not Manalink. It's at 0x7a5380 there.

There's only 16 dynamically-created card_data_t's, not 16*5. And their indices aren't stored in the memory after 0x7a5380 anyway. So you want to be iterating from cards_data[EXE_DWORD(0x7a5380)] to cards_data[EXE_DWORD(0x7a5380) + 15] inclusive. end_of_dynamic_types is just start_of_dynamic_types + 16.

You'll find yourself running out of dynamic types (the "AddType error: No room in CT to add another type" popup) very, very frequently if you don't add in something to reclaim unused ones more often than just during end_turn_phase(). Shandalar does it directly in create_a_card_type().

I haven't looked at most of the rest, but two things jump out at me:
  • card_data_t::subtype doesn't do what you think it does. Manalink has no provision for Shandalar's per-card_data_t assignable subtypes (let alone per-card_instance_t).
  • The goto-epilogue pattern only makes sense if you're doing something nontrivial in your function epilogue. Just "return int_id" instead.
The bigcard doesn't just use the original card type's power and toughness; it doesn't look at the current card type at all, except for replacing sleight and hack text. Compare what's in Manalink's draw_fullcard_normal() and Shandalar's.

Re: Card Development - talk about cards code here

PostPosted: 22 Dec 2020, 19:04
by Aswan jaguar
I made Soul Kiss and tried to give it AI code for pre attack defend decisions using for first time in manalink EVENT_CHECK_ABILITIES. My code based on shandalar implementation has the issue that AI doesn't get that my enchanted creature can boost more than once. Also AI sees only toughness boost activation for one time but not activation for one time for power. Is the power boost (for once), still issue of my code or EXE_DWORD(0x789B50) is not the one responsible for power boost?

Code: Select all
int card_soul_kiss(int player, int card, event_t event){
   /* Soul Kiss   |2|B
    * Enchantment - Aura
    * Enchant creature
    * |B, Pay 1 life: Enchanted creature gets +2/+2 until end of turn. Activate this ability no more than three times each turn. */

   if (event == EVENT_SHOULD_AI_PLAY){
      ai_modifier += (get_card_instance(player, card)->damage_target_player != player) ? 0 : (player == AI ? 12 : -12) * landsofcolor_controlled[player][COLOR_BLACK];
   }

   card_instance_t* inst = get_card_instance(player, card);

   if ((event == EVENT_CHECK_ABILITIES && affect_me(inst->damage_target_player, inst->damage_target_card)
        && inst->damage_target_player == player && in_play(player,card))){

      int times = 3 - MAX(0, inst->targets[18].player);
      times = MIN(times, can_pay_life_as_cost_for_spells_or_activated_abilities(player, 1));
      if( times > 0 ){
         times = MIN(times, has_mana(player, COLOR_BLACK, 1));
         EXE_DWORD(0x789B50) += 2 * times; //  analogous to EVENT_POW_BOOST for arbitrary cards see EVENT_CHECK_ABILITIES in defs.h for more details
         EXE_DWORD(0x7A371C) += 2 * times; //  analogous to EVENT_TOU_BOOST for arbitrary cards see EVENT_CHECK_ABILITIES in defs.h
      }
   }

   if( get_card_instance(player, card)->damage_target_player > -1 ){

      if( IS_GAA_EVENT(event) ){

         if( event == EVENT_CAN_ACTIVATE ){
            if( get_card_instance(player, card)->targets[18].player == 3 ){
               return 0;
            }
         }

         if( event == EVENT_ACTIVATE ){
            generic_activated_ability(player, card, event, 0, MANACOST_B(1), 1, NULL, NULL);
            if( spell_fizzled != 1 ){
               get_card_instance(player, card)->targets[18].player++;
            }
            return 0;
         }

         if (event == EVENT_RESOLVE_ACTIVATION){
            card_instance_t* instance = get_card_instance(player, card);

            if (in_play(instance->damage_target_player, instance->damage_target_card))
            pump_until_eot_merge_previous(instance->parent_controller, instance->parent_card, instance->damage_target_player, instance->damage_target_card, 2, 2);
         }

         if( event == EVENT_CLEANUP ){
            get_card_instance(player, card)->targets[18].player = 0;
         }

         return generic_activated_ability(player, card, event, 0, MANACOST_B(1), 1, NULL, NULL);
      }
   }

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

   return vanilla_aura(player, card, event, player);
}

Re: Card Development - talk about cards code here

PostPosted: 23 Dec 2020, 02:20
by Korath
can_pay_life_as_cost_for_spells_or_activated_abilities() only ever returns 1 or 0, so times is never > 1.

check_abilities_power is at 0x739b50, not 0x789b50. Sorry I got it wrong in the comment in defs.h. To confirm, disassemble 0x4af2ff to 0x4af33e in setup_ai_combat_data() in Magic.exe; that's the equivalent of
Code: Select all
check_abilities_power = get_abilities(player, card, EVENT_POWER, -1);
check_abilities_toughness = get_abilities(player, card, EVENT_TOUGHNESS, -1);
check_abilities_keywords = get_abilities(player, card, EVENT_ABILITIES, -1);

Re: Card Development - talk about cards code here

PostPosted: 23 Dec 2020, 07:56
by Aswan jaguar
Thanks, Korath for confirming that it was not the correct function.
Korath wrote: To confirm, disassemble 0x4af2ff to 0x4af33e in setup_ai_combat_data() in Magic.exe; that's the equivalent of:...
.
I wouldn't know even how to start. Which are the proper tools to begin with and then the process I guess is not a walk on the park.

Could you please insert the two updates to setup_ai_combat_abilities() function in magic.exe so that AI can see it's own creatures can boost, too. You said here: viewtopic.php?f=86&t=19099&p=201447&hilit=kessig+wolf#p201454

Re: Card Development - talk about cards code here

PostPosted: 23 Dec 2020, 10:29
by Korath
Aswan jaguar wrote:Thanks, Korath for confirming that it was not the correct function.
variable
I wouldn't know even how to start. Which are the proper tools to begin with and then the process I guess is not a walk on the park.
From the command line:
Code: Select all
$ objdump -M intel -d Magic.exe --start-address=0x4af2ff --stop-address=0x4af344

Magic.exe:     file format pei-i386


Disassembly of section .text:

004af2ff <.text+0xae2ff>:
  4af2ff:       6a ff                   push   0xffffffff
  4af301:       6a 32                   push   0x32
  4af303:       ff 75 f0                push   DWORD PTR [ebp-0x10]
  4af306:       ff 75 08                push   DWORD PTR [ebp+0x8]
  4af309:       e8 c2 5f f8 ff          call   0x4352d0
  4af30e:       83 c4 10                add    esp,0x10
  4af311:       a3 50 9b 73 00          mov    ds:0x739b50,eax
  4af316:       6a ff                   push   0xffffffff
  4af318:       6a 33                   push   0x33
  4af31a:       ff 75 f0                push   DWORD PTR [ebp-0x10]
  4af31d:       ff 75 08                push   DWORD PTR [ebp+0x8]
  4af320:       e8 ab 5f f8 ff          call   0x4352d0
  4af325:       83 c4 10                add    esp,0x10
  4af328:       a3 1c 37 7a 00          mov    ds:0x7a371c,eax
  4af32d:       6a ff                   push   0xffffffff
  4af32f:       6a 34                   push   0x34
  4af331:       ff 75 f0                push   DWORD PTR [ebp-0x10]
  4af334:       ff 75 08                push   DWORD PTR [ebp+0x8]
  4af337:       e8 94 5f f8 ff          call   0x4352d0
  4af33c:       83 c4 10                add    esp,0x10
  4af33f:       a3 f0 fe 78 00          mov    ds:0x78fef0,eax
From gdb:
Code: Select all
$ gdb -q Magic.exe
Reading symbols from Magic.exe...(no debugging symbols found)...done.
(gdb) set disassembly intel
(gdb) disas 0x4af2ff,0x4af344
Dump of assembler code from 0x4af2ff to 0x4af344:
   0x004af2ff <SellPrice+98831>:        push   0xffffffff
   0x004af301 <SellPrice+98833>:        push   0x32
   0x004af303 <SellPrice+98835>:        push   DWORD PTR [ebp-0x10]
   0x004af306 <SellPrice+98838>:        push   DWORD PTR [ebp+0x8]
   0x004af309 <SellPrice+98841>:        call   0x4352d0
   0x004af30e <SellPrice+98846>:        add    esp,0x10
   0x004af311 <SellPrice+98849>:        mov    ds:0x739b50,eax
   0x004af316 <SellPrice+98854>:        push   0xffffffff
   0x004af318 <SellPrice+98856>:        push   0x33
   0x004af31a <SellPrice+98858>:        push   DWORD PTR [ebp-0x10]
   0x004af31d <SellPrice+98861>:        push   DWORD PTR [ebp+0x8]
   0x004af320 <SellPrice+98864>:        call   0x4352d0
   0x004af325 <SellPrice+98869>:        add    esp,0x10
   0x004af328 <SellPrice+98872>:        mov    ds:0x7a371c,eax
   0x004af32d <SellPrice+98877>:        push   0xffffffff
   0x004af32f <SellPrice+98879>:        push   0x34
   0x004af331 <SellPrice+98881>:        push   DWORD PTR [ebp-0x10]
   0x004af334 <SellPrice+98884>:        push   DWORD PTR [ebp+0x8]
   0x004af337 <SellPrice+98887>:        call   0x4352d0
   0x004af33c <SellPrice+98892>:        add    esp,0x10
   0x004af33f <SellPrice+98895>:        mov    ds:0x78fef0,eax
End of assembler dump.
(In both, I tacked on another instruction at the end to show the location of check_abilities_keywords.)

IDA is handier, but a steeper learning curve.
Could you please insert the two updates to setup_ai_combat_abilities() function in magic.exe so that AI can see it's own creatures can boost, too.
6b10de9af.

Re: Card Development - talk about cards code here

PostPosted: 01 Jan 2021, 17:48
by drool66
Does anyone know why some of the split cards have the casting cost of their second half in their csv entries? It came up when I was coding Expansion // Explosion, and I noticed it in Supply // Demand as well. Once I changed the values to match the casting cost of the first half the spells functioned correctly. Before I go through an check all 58 split cards, I want to make sure this wasn't perhaps done intentionally. Also, Korath if you see this, would changing these values affect Shandalar?

Re: Card Development - talk about cards code here

PostPosted: 01 Jan 2021, 19:17
by Korath
Shandalar implements split (and aftermath, and adventurer) cards by having separate cards for each named side. It throws away the main card's casting cost and reconstructs it from the halves (or fifths, for Who // What // When // Where // Why).

In general, though, don't worry about it. If there's bad data, I'll just change it at runtime.

Re: Card Development - talk about cards code here

PostPosted: 02 Jan 2021, 09:26
by Aswan jaguar
I don't see anything wrong in Manalink.csv or ct_all.csv for Expansion // Explosion.
For Supply // Demand the Manalink.csv values are wrong. The ct_all.csv values are fine. The card was coded originally backwards as Demand // Supply and that lead to this mistake probably. I fixed in the code that and some other bugs in commit ce5de84 ("Add rest AHK cards with aftermath", 2020-10-24) and Manalink.csv locally but forgot to change Manalink.csv in source.