It is currently 27 Apr 2024, 17:39
   
Text Size

Card Development - talk about cards code here

Discuss Upcoming Releases, Coding New Cards, Etc.
PLEASE DO NOT REPORT BUGS HERE!

Moderators: BAgate, drool66, Aswan jaguar, gmzombie, stassy, CCGHQ Admins

Infectious Rage

Postby Aswan jaguar » 02 Nov 2023, 16:00

I tried again on Infectious Rage and it's in better shape now than the draft I uploaded 3 months ago, the effect is random and most times it works but still sometimes it goes to graveyard although there are creatures it can enchant. Also do I need a check for can enchant other than the one put_into_play_aura_attached_to_target()? Can this be done correctly because I see few functions in shandalar's code that we don't have in manalink.

Code: Select all
static int infectious_rage_legacy(int player, int card, event_t event){
   if( resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY) ){
      card_instance_t *instance = get_card_instance(player, card);
      int owner = instance->targets[0].player;
      int iid = instance->targets[0].card;

      // Sometimes it goes to graveyard although there are creatures it can enchant. Also there is no check for can enchant.
      int marked[151] = {0};
      marked[0] = -1;
      int num_found = 0;
      int num_pl;
      CYCLE_ALL_CARDS(p, c,    {
                           if( in_play(p, c) && is_what(p, c, TYPE_CREATURE) ){
                              marked[num_found] = c;
                              num_found++;
                              num_pl = p;
                           }
                        };
      );

      if( num_found ){
         int rnd = network_rand(player, num_found);
         int result = seek_grave_for_id_to_reanimate(owner, -1, owner, cards_data[iid].id, REANIMATEXTRA_RETURN_TO_HAND2);
         if( result > 0 ){
            put_into_play_aura_attached_to_target(owner, result, num_pl, marked[rnd]);
         }
      }

      kill_card(player, card, KILL_EXILE);
   }
   return 0;
}
int card_infectious_rage(int player, int card, event_t event){
   /* CARD_ID_INFECTIOUS_RAGE   5960 // remain
   Infectious Rage   |1|R
   Enchantment - Aura
   Enchant creature
   Enchanted creature gets +2/-1.
   When enchanted creature dies, choose a creature at random ~ can enchant. Return ~ to the battlefield attached to that creature. */

   if (event == EVENT_CHECK_PUMP)   // will only be sent to this card if it has flash, e.g. for Feebleness
      return vanilla_instant_pump(player, card, event, ANYBODY, 1-player, 2, -1, 0, 0);

   card_instance_t *instance;
   if( event == EVENT_GRAVEYARD_FROM_PLAY && (instance = get_card_instance(player, card)) && instance->damage_target_player > -1
      && ! is_humiliated(player, card, AL_TRIG) )
   {
      int p = instance->damage_target_player;
      int c = instance->damage_target_card;
      if( affect_me(p, c) && is_this_dying(p, c) && ! is_token(p, c) && !check_for_death_trigger_removal(p, c) ){
         set_special_flags3(player, card, SF3_ENCHANTED_PERMANENT_DYING);
      }
      if( affect_me(player, card) && in_play(player, card) && !check_for_death_trigger_removal(p, c) && is_this_dying(player, card) ){
         if( check_special_flags3(player, card, SF3_ENCHANTED_PERMANENT_DYING) ){
            int legacy = create_legacy_effect(player, card, &infectious_rage_legacy);
            card_instance_t *leg = get_card_instance(player, legacy);
            leg->targets[0].player = get_owner(player, card);
            leg->targets[0].card = get_original_internal_card_id(player, card);
            leg->targets[11].player = 1;
         }
      }
   }
   attached_gets_pt(player, card, event, 2, -1);
   return aura_pref_controller(player, card, event, ANYBODY, NULL);
}
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 8078
Joined: 13 May 2010, 12:17
Has thanked: 730 times
Been thanked: 458 times

Re: Card Development - talk about cards code here

Postby drool66 » 03 Nov 2023, 04:25

I see few functions in shandalar's code that we don't have in manalink.
With this card, not really. We don't have all the same ways to access it, but we do have all the same machinery.

Primary problem with this code is num_pl will always be set to the last player with a creature on the bf, rather than being associated with the card it matches with. you need to declare marked[2][151];

and set:

marked[0][num_found] = p;
marked[1][num_found] = c;

Or better yet, declare
target_t marked[2*MAX_CARD_INSTANCE];

and set
marked[num_found].player = p;
marked[num_found].card = c;

Just checking is_what(... TYPE_CREATURE) isn't a great check; it should be more along the lines of (from aura_impl):
target_definition_t td;
default_target_definition(player, card, [[ or inst->parent_, or whatever is appropriate ]] &td, TYPE_CREATURE);
td->flags |= TARGET_FLAG_IGNORE_SHROUD | TARGET_FLAG_FROM_AURA;
int this_csvid = get_id(p, c);
if( validate_arbitrary_target(&td, p, c) &&
!((! is_humiliated(p, c, AL_STATIC) &&
(this_csvid == CARD_ID_TETSUO_UMEZAWA || this_csvid == CARD_ID_BARTEL_RUNEAXE)))
){...

I think Infectious Rage can just return enchant_creature() - I don't see any reason preferred_controller is relevant if it's set to ANYBODY.

You should also store the player & card value for infectious rage in the legacy and check find_in_owners_graveyard() (but you don't really need a legacy because...)

This is a great card to demonstrate the new death triggers rolled out in 1b7fee4. I think it would begin with something like :

STRIGGER((event == EVENT_DIES && affected_card != -1 && get_card_instance(affected_card_controller, affected_card) != NULL && (inst = get_card_instance(player, card))->damage_target_player == affected_card_controller && inst->damage_target_card == affected_card),
{
int owner;
int position;
if(find_in_owners_graveyard(t.player, t.card, &owner, &position){
target_t marked[2*MAX_CARD_INSTANCE];
...
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Card Development - talk about cards code here

Postby drool66 » 05 Nov 2023, 17:15

Here's where I ended up:
Code: Select all
int card_infectious_rage(int player, int card, event_t event){
   /* CARD_ID_INFECTIOUS_RAGE   5960 // remain
   Infectious Rage   |1|R
   Enchantment - Aura
   Enchant creature
   Enchanted creature gets +2/-1.
   When enchanted creature dies, choose a creature at random ~ can enchant. Return ~ to the battlefield attached to that creature. */

   STRIGGER(event == EVENT_DIES && get_card_instance(player, card)->damage_target_player == affected_card_controller && get_card_instance(player, card)->damage_target_card == affected_card && affected_card != -1,
      int owner;
      int position;
      if(find_in_owners_graveyard(t.player, t.card, &owner, &position)){
         target_t marked[2*MAX_CARD_INSTANCE];
         int num_found = 0;
         target_definition_t td;
         default_target_definition(player, card, &td, TYPE_CREATURE);
         td.flags |= TARGET_FLAG_IGNORE_SHROUD | TARGET_FLAG_FROM_AURA;
         CYCLE_ALL_CARDS(p, c,
                           {
                              int this_csvid = get_id(p, c);
                              if( validate_arbitrary_target(&td, p, c) &&
                                    !(! is_humiliated(p, c, AL_STATIC) &&
                                    (this_csvid == CARD_ID_TETSUO_UMEZAWA || this_csvid == CARD_ID_BARTEL_RUNEAXE))
                              ){
                                 marked[num_found].player = p;
                                 marked[num_found].card = c;
                                 num_found++;
                              }
                           };
         );
         if( num_found ){
            int chosen = num_found == 1 ? 0 : network_rand(player, num_found-1);
            int card_added = add_card_to_hand(owner, get_grave(owner)[position]);
            remove_card_from_grave(owner, position, TUTOR_PLAY);
            put_into_play_aura_attached_to_target(owner, card_added, marked[chosen].player, marked[chosen].card);
         }
      });

   attached_gets_pt(player, card, event, 2, -1);
   return enchant_creature(player, card, event);
}
Only problem is it uncovered a problem with triggers where the game will hang. Probably something in main_phase(), but it could be a good number of places. Back to the drawing board for me.
It would also be good to put an argument for damage_target_player/_card in reanimate_permanent_impl() - would also let us reclaim a few reanimate_mode_t bits
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

[fixed]Student of Elements // Tobita, Master of Windslate fl

Postby Aswan jaguar » 24 Jan 2024, 12:07

Student of Elements // Tobita, Master of Winds a flip card, my issue is that the flip happens late, it needs a step or two or an action to take effect. We have met this before if I remember correctly few times and in some we at least we fixed it.
Code: Select all
int card_student_of_elements(int player, int card, event_t event){
   /* CARD_ID_STUDENT_OF_ELEMENTS   7397
   Student of Elements   |1|U
   Creature - Human Wizard 1/1
   When ~ has flying, flip it.
   --FLIP--
   Tobita, Master of Winds
   Legendary Creature - Human Wizard
   3/3
   Creatures you control have flying. */

   double_faced_card(player, card, event);
   // in progress works but the flip is late needs a step or two or an action to take effect
   if( event == EVENT_ABILITIES && affect_me(player, card) && in_play(player, card) && ! is_humiliated(player, card, AL_STATIC) ){
      if( check_for_ability(player, card, KEYWORD_FLYING) ){
         true_transform(player, card);
         verify_legend_rule(player, card, get_id(player, card));
      }
   }

   return 0;
}
Last edited by Aswan jaguar on 21 Mar 2024, 13:10, edited 1 time in total.
Reason: fixed
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 8078
Joined: 13 May 2010, 12:17
Has thanked: 730 times
Been thanked: 458 times

Re: Card Development - talk about cards code here

Postby drool66 » 29 Jan 2024, 06:50

Have you tried checking for the ability during EVENT_STATIC_EFFECTS instead?
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Card Development - talk about cards code here

Postby Aswan jaguar » 30 Jan 2024, 19:47

drool66 wrote:Have you tried checking for the ability during EVENT_STATIC_EFFECTS instead?
Same result.
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 8078
Joined: 13 May 2010, 12:17
Has thanked: 730 times
Been thanked: 458 times

Cruel Deceiver 2nd ability works only if already a damage le

Postby Aswan jaguar » 31 Jan 2024, 15:03

I tried a lot but I can't find how to do this correctly. Cruel Deceiver's 2nd ability works only if there is attached a previous effect (for instance if it deals combat damage untaps somehow and then deals damage as a result of it being enchanted by Fire Whip and activate first ability ) on target creature.
Code: Select all
// If there is already a damage legacy attached to the creature then it kills it's target
static int whenever_this_deals_damage_to_a_creature_destroy_that_creature(int player, int card, event_t event){
   if( event == EVENT_RESOLVE_TRIGGER ){
      card_instance_t *dmg = get_card_instance(player, card);
      //kill_card(dmg->damage_target_player, dmg->damage_target_card, KILL_DESTROY);
      kill_card(dmg->targets[1].player, dmg->targets[1].card, KILL_DESTROY); // no change from above

      kill_card(player, card, KILL_EXILE);
   }
   return 0;
}

void cruel_deceiver_effect(int player, int card, int t_player, int t_card){
   int legacy = create_targetted_legacy_effect(player, card, &whenever_this_deals_damage_to_a_creature_destroy_that_creature, t_player, t_card);
   card_instance_t *instance = get_card_instance( player, legacy );
   instance->targets[0].player = player;
   instance->targets[0].card = card;
   instance->targets[1].player = t_player;
   instance->targets[1].card = t_card;
   instance->number_of_targets = 2;
}

static int cruel_deceiver_activated(int player, int card, event_t event){
   int p = get_card_instance(player, card)->targets[0].player;
   int c = get_card_instance(player, card)->targets[0].card;
   if( ! is_humiliated(p, c, AL_TRIG) ){
      for_each_creature_damaged_by_me(p, c, event, 0, &cruel_deceiver_effect, p, c);
   }

   if( event == EVENT_CLEANUP )
   kill_card(player, card, KILL_EXILE);

   return 0;
}
int card_cruel_deceiver(int player, int card, event_t event){ // in progress 2nd works only If there is already a damage legacy attached to the creature then it kills it's target
   /* CARD_ID_CRUEL_DECEIVER   7186
   Cruel Deceiver   |1|B
   Creature - Spirit 2/1
   |1: Look at the top card of your library.
   |2: Reveal the top card of your library. If it's a land card, ~ gains "Whenever ~ deals damage to a creature, destroy that creature" until end of turn.
    Activate only once each turn. */

   if( event == EVENT_CHANGE_TYPE && affect_me(player, card) && ! is_humiliated(player, card, AL_TRIG) )
   {
      DECL_INST();
      inst->destroys_if_blocked |= DIFB_DESTROYS_UNPROTECTED;
   }


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

   enum{
      CHOICE_LOOK = 1,
      CHOICE_REVEAL
   };

   if( event == EVENT_CAN_ACTIVATE ){
      return generic_activated_ability(player, card, event, GAA_NONE, MANACOST_X(1), 0, NULL, NULL);
   }

   if( event == EVENT_ACTIVATE ){
      DECL_INST();
      int look = generic_activated_ability(player, card, EVENT_CAN_ACTIVATE, GAA_NONE, MANACOST_X(1), 0, NULL, NULL);
      int reveal = generic_activated_ability(player, card, EVENT_CAN_ACTIVATE, GAA_ONCE_PER_TURN, MANACOST_X(2), 0, NULL, NULL);
      int choice = DIALOG(player, card, event, DLG_RANDOM, DLG_AUTOCHOOSE_IF_1, DLG_MODE,
                     "Look top card of your library", look, 2,
                     "Reveal top card of your library", reveal, 50);
      if( ! choice ){
         spell_fizzled = 1;
         return 0;
      }
      choice = inst->mode;
      if( choice == CHOICE_LOOK ){
         return generic_activated_ability(player, card, event, GAA_NONE, MANACOST_X(1), 0, NULL, NULL);
      }
      if( choice == CHOICE_REVEAL ){
         return generic_activated_ability(player, card, event, GAA_ONCE_PER_TURN, MANACOST_X(2), 0, NULL, NULL);
      }
   }

   if( event == EVENT_RESOLVE_ACTIVATION && deck_ptr[player][0] != -1 ){
      DECL_INST();
      int choice = inst->mode;
      if( choice == CHOICE_LOOK ){
         int *deck = deck_ptr[player];
         show_deck( player, deck, 1, "This is the top card of your library", 0, str_Done );
      }
      if( choice == CHOICE_REVEAL ){
         reveal_card_iid(player, card, deck_ptr[player][0]);
         if( is_what(-1, deck_ptr[player][0], TYPE_LAND) ){
            int legacy = create_targetted_legacy_effect(player, card, &cruel_deceiver_activated, inst->parent_controller, inst->parent_card);
            card_instance_t *leg = get_card_instance(player, legacy);
            leg->targets[0].player = inst->parent_controller;
            leg->targets[0].card = inst->parent_card;
            leg->number_of_targets = 1;
         }
      }
   }

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

   return 0;
}
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 8078
Joined: 13 May 2010, 12:17
Has thanked: 730 times
Been thanked: 458 times

Re: Card Development - talk about cards code here

Postby drool66 » 10 Feb 2024, 17:31

I think for Student of Elements, you'll have to do something new in get_abilities(); either special case this card or create a new event to dispatch. I think after the last is_humiliated() checks and rechecks is the right place to do it.
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Card Development - talk about cards code here

Postby Aswan jaguar » 13 Feb 2024, 19:59

Student of Elements if I use transform() instead of true_transform() then my issue gets fixed but I don't know if I cause other issues as it is not used for any other flip cards and only used for true double faced cards. The mix of these functions between flip cards and double faced cards and ... confuses me.
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 8078
Joined: 13 May 2010, 12:17
Has thanked: 730 times
Been thanked: 458 times

Re: Card Development - talk about cards code here

Postby drool66 » 16 Feb 2024, 16:42

Ah, that would explain the delay. true_transform() writes the relevant data to the card instance with the notable exception of ::internal_card_id. transform() calls true_transform() and then adds KEYWORD_RECALC_ALL and calls get_abilities() to actually transform the card. If you only call true_transform(), you have to wait until the next time iids are recalculated in get_abilities() by something else. The problem is transform() will also call / dispatch EVENT_TRANSFORMED and EVENT_ANOTHER_PERMANENT_HAS_TRANSFORMED, which will make Kamigawa flip cards interact improperly with cards that respond to these events (currently only Cult of the Waxing Moon and Neglected Heirloom) We should make a function for Kamigawa flip cards that is identical to transform() except that it doesn't call these two events. I would call it "flip_card()" except that's the name of the function for turning face-down cards face-up so maybe "flip_kamigawa()" or something? I don't know
User avatar
drool66
Programmer
 
Posts: 1163
Joined: 25 Nov 2010, 22:38
Has thanked: 186 times
Been thanked: 267 times

Re: Card Development - talk about cards code here

Postby Aswan jaguar » 19 Feb 2024, 14:47

Never mind I solved the issue for Student of Elements // Tobita, Master of Winds.

I made a kamigawa_flip() after transform() and stripped it down to:
Code: Select all
void kamigawa_flip(int player, int card)
{
   true_transform(player, card);

   card_instance_t* instance = get_card_instance(player, card);
   instance->regen_status |= KEYWORD_RECALC_ALL;
   get_abilities(player, card, EVENT_CHANGE_TYPE, -1); // needed for the instant change.
}
Now the flip is instant if I use Flying Carpet to give it flying but not if I use Flight (which I have changed locally to use aura()).
Another issue is that after the flip it will affect instantly and give flying only to creatures with bigger in game id. The ones with lower ids will have to wait until the next time iids are recalculated in get_abilities() by something else.
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 8078
Joined: 13 May 2010, 12:17
Has thanked: 730 times
Been thanked: 458 times

Previous

Return to Development

Who is online

Users browsing this forum: No registered users and 36 guests


Who is online

In total there are 36 users online :: 0 registered, 0 hidden and 36 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 36 guests

Login Form