It is currently 30 Sep 2020, 06:13
   
Text Size

[Fixed]Blessed Alliance

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

[Fixed]Blessed Alliance

Postby Aswan jaguar » 12 May 2019, 13:05

Describe the Bug:
1- Blessed Alliance 1st ability "gain life" doesn't target player always gives 4 life to controller.
2- 3nd ability "target opponent sacrifice" acts exactly like 2nd ability "untap up to 2 creatures" + 1st it gives 4 life to controller.
3- when you escalate you don't get a prompt and cost is free of charge.
Which card did behave improperly?
Blessed Alliance

Which update are you using? (date, name)Which type? (duel, gauntlet, sealed deck)
Manalink dev 250c78bd version - duel

What exactly should be the correct behavior/interaction?
1- Blessed Alliance 1st ability targets any player.
2- 3nd ability: Target opponent sacrifices an attacking creature.
3- when you escalate you get a prompt/get charged to pay the escalate cost.
Are any other cards possibly affected by this bug?
-
Last edited by Aswan jaguar on 28 Sep 2020, 12:10, edited 2 times in total.
Reason: fixed
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 7213
Joined: 13 May 2010, 12:17
Has thanked: 596 times
Been thanked: 327 times

Re: Blessed Alliance

Postby Aswan jaguar » 15 May 2019, 08:50

3nd bug fixed in commit 3936db6f.
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 7213
Joined: 13 May 2010, 12:17
Has thanked: 596 times
Been thanked: 327 times

Re: Blessed Alliance

Postby gnomefry » 09 Aug 2020, 20:21

Confirming #2 and #3 still borked.
User avatar
gnomefry
 
Posts: 151
Joined: 28 Dec 2018, 00:44
Has thanked: 13 times
Been thanked: 5 times

Re: [confirmed]Blessed Alliance

Postby FastEddie » 21 Aug 2020, 15:42

This should debork it (or do you say unbork ?).

The choice from the dialog was not converted into bits but taken as a value (#2) and a player choice was missing (#1). I modelled the latter according to Healing Salve, hope that is ok. Savegame for testing is attached.
Attachments
BlessedAlliance.zip
(3.52 KiB) Downloaded 11 times
eldrich_moon.zip
(862 Bytes) Downloaded 9 times
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 194
Joined: 24 Dec 2019, 10:59
Has thanked: 13 times
Been thanked: 16 times

Re: [confirmed]Blessed Alliance

Postby Aswan jaguar » 23 Aug 2020, 15:45

Your implementation fixes the original bugs #1 & #2 but doesn't work with escalate. To be specific the gain life ability doesn't work correctly with any of the other two abilities when escalated. It has to use base_target to do that. I tried a lot and made 1st to work with 3nd ability but it is beyond me to get how to overcome the issue that the target storage is overwritten when you escalate 1st and 2nd abilities. Also if I understand correctly for escalate to work you need to use & not == operator.
This is my attempt:
Code: Select all
int card_blessed_alliance(int player, int card, event_t event){
/* Blessed Alliance   |1|W   0x200ef08
 * Instant
 * Escalate |2
 * Choose one or more -
 * * Target player gains 4 life.
 * * Untap up to two target creatures.
 * * Target opponent sacrifices an attacking creature. */

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

   enum
   {
      CHOICE_GAIN_LIFE    = 1<<0,
      CHOICE_UNTAP       = 1<<1,
      CHOICE_OPP_SAC       = 1<<2,
      CHOICE_MASK = CHOICE_GAIN_LIFE | CHOICE_UNTAP | CHOICE_OPP_SAC,
   };

   target_definition_t td;
   default_target_definition(player, card, &td, TYPE_CREATURE );
   td.preferred_controller = player;

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

   test_definition_t test;
   default_test_definition(&test, TYPE_CREATURE);
   test.state = STATE_ATTACKING;

   card_instance_t *instance = get_card_instance(player, card);

   if( event == EVENT_CAN_CAST ){
      if( basic_spell(player, card, event) ){
         return can_target(&td) | can_target(&td2);
      }
   }

   if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
      instance->number_of_targets = 0;
      int i;
      if( ! check_special_flags2(player, card, SF2_COPIED_FROM_STACK) ){
         instance->info_slot = 0;
         while( (instance->info_slot & CHOICE_MASK) != CHOICE_MASK ){
               int can_untap = !(instance->info_slot & CHOICE_UNTAP) && can_target(&td) ? 1 : 0;
               int can_target_opp = !(instance->info_slot & CHOICE_OPP_SAC) && would_validate_arbitrary_target(&td2, 1-player, -1) ? 1 : 0;
               if( player == AI && ! new_can_sacrifice(player, card, 1-player, &test) ){
                  can_target_opp = 0;
               }
               int choice = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_RANDOM,
                              "Target player gains 4 life", !(instance->info_slot & CHOICE_GAIN_LIFE), 5,
                              "Untap up to two target creatures", can_untap, 10,
                              "Target opponent sacrifices an attacking creature", can_target_opp, 15);

               if( ! choice ){
                  spell_fizzled = 1;
                  return 0;
               }
               // Convert choice number 1, 2, 3 in choice bits 001, 010, 100
               instance->info_slot |= 1<<(choice-1);
               if( has_mana(player, COLOR_ANY, 2) ){
                  int choice2 = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_NO_CANCEL,
                                 "Escalate", 1, 10,
                                 "Decline", 1, 1);
                  if( choice2 == 1 ){
                     charge_mana(player, COLOR_ANY, 2);
                     if( spell_fizzled == 1 ){
                        spell_fizzled = 0;
                        break;
                     }
                  }
                  else{
                     break;
                  }
               }
               else{
                  break;
               }
         }
      }
      int base_target = 0;
      if( instance->info_slot & CHOICE_GAIN_LIFE ){
         if( new_pick_target(&td2, "TARGET_PLAYER", base_target, 1) ){
            base_target++;
         }
         else{
            return 0;
         }
      }
      
      if( instance->info_slot & CHOICE_UNTAP ){
         for(i=0; i<2; i++){
            if( new_pick_target(&td, "TARGET_CREATURE", base_target, 1) ){
               state_untargettable(instance->targets[base_target].player, instance->targets[base_target].card, 1);
               base_target++;
            }
            else{
               break;
            }
         }
         for(i=0; i<instance->number_of_targets; i++){
            state_untargettable(instance->targets[i].player, instance->targets[i].card, 0);
         }
      }
      if( instance->info_slot & CHOICE_OPP_SAC ){
         instance->targets[base_target].player = 1-player;
         instance->targets[base_target].card = -1;
         instance->number_of_targets++;
      }
   }

   if( event == EVENT_RESOLVE_SPELL ){
      int base_target = 0;
        if( instance->info_slot & CHOICE_GAIN_LIFE ){
         if( validate_target(player, card, &td2, base_target) ){
            gain_life(instance->targets[base_target].player, 4);
         }
         base_target++;
      }

      if( instance->info_slot & CHOICE_UNTAP ){
         int i;
         for(i=0; i<instance->number_of_targets; i++){
            if( instance->targets[i].card != -1 ){
               if( validate_target(player, card, &td, i) ){
                  untap_card(instance->targets[i].player, instance->targets[i].card);
               }
               base_target++;
            }
         }
      }
      if( instance->info_slot & CHOICE_OPP_SAC ){
         if( validate_target(player, card, &td2, base_target) ){
            new_sacrifice(player, card, 1-player, SAC_NO_CANCEL | SAC_CAUSED, &test);
         }
      }
      kill_card(player, card, KILL_DESTROY);
   }

   return 0;
}
Also applied your fix to all other 3 mode cards with escalate that also had the same issue for 3nd ability (committed in 5dc1377):
Code: Select all
instance->info_slot |= 1<<(choice-1);
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 7213
Joined: 13 May 2010, 12:17
Has thanked: 596 times
Been thanked: 327 times

Re: [confirmed]Blessed Alliance

Postby FastEddie » 24 Aug 2020, 16:39

I fear you caught me flat footed on that :oops: . I was so happy that gain life worked that I forgot to test the escalation. Also the & operator is correct, copy-paste error on my end.

After that I went through all permutations and tested each based on your code, namely

1, 2, 3, 1+2, 1+3, 2+3, 2+1, 3+1, 3+2, 1+2+3, 1+3+2, 2+1+3, 2+3+1, 3+1+2, 3+2+1

(these should be all - the order shouldn't matter but it wanted to be sure).

All of them worked, the only issue is that the whole spell fizzles if you don't choose two targets for option 2 (it says "up to two"). The issue seems to be validate_target(player, card, &td, i) in

Code: Select all
      if( instance->info_slot & CHOICE_UNTAP ){
         int i;
         for(i=0; i<instance->number_of_targets; i++){
            if( instance->targets[i].card != -1 ){
               if( validate_target(player, card, &td, i) ){
                  untap_card(instance->targets[i].player, instance->targets[i].card);
               }
               base_target++;
            }
         }
      }
judging from manalink.h where it says

Code: Select all
// Should be used only when actually validating an already-chosen target, i.e. during EVENT_RESOLVE_SPELL, EVENT_RESOLVE_ACTIVATION, resolution of a trigger, etc.
What do you think?
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 194
Joined: 24 Dec 2019, 10:59
Has thanked: 13 times
Been thanked: 16 times

Re: [confirmed]Blessed Alliance

Postby FastEddie » 13 Sep 2020, 15:43

I think I have it... double book keeping is not always a boon :).

The main issue was that the book keeping was all over the place because of the two indices, number_of_targets and base_target. I switched the cast part solely to number_of_targets and renamed base_target to targets_found for the resolve part as this is what it is actually about.
The escalate mechanism is slightly adjusted (a second condition before the DIALOG) to make sure the player can only escalate twice (three times would force you to cancel).
That the spell fizzled if you chose only one creature to untap was a feature as the last argument of new_pick_target was 1 instead of 0. But not an intented one.

I haven't looked at the other bugged escalate cards but I guess the book keeping issue is the same there (copy, paste and pray as Korath succinctly put it).

Now it looks like this (comments with the card description are properly indented I think):

Code: Select all
int card_blessed_alliance(int player, int card, event_t event){
 /* Blessed Alliance   |1|W   0x200ef08
  * Instant
  * Escalate |2
  * Choose one or more -
  * * Target player gains 4 life.
  * * Untap up to two target creatures.
  * * Target opponent sacrifices an attacking creature. */

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

   enum
   {
      CHOICE_GAIN_LIFE    = 1<<0,
      CHOICE_UNTAP       = 1<<1,
      CHOICE_OPP_SAC       = 1<<2,
      CHOICE_MASK = CHOICE_GAIN_LIFE | CHOICE_UNTAP | CHOICE_OPP_SAC,
   };

   target_definition_t td;
   default_target_definition(player, card, &td, TYPE_CREATURE );
   td.preferred_controller = player;

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

   test_definition_t test;
   default_test_definition(&test, TYPE_CREATURE);
   test.state = STATE_ATTACKING;

   card_instance_t *instance = get_card_instance(player, card);

   if( event == EVENT_CAN_CAST ){
      if( basic_spell(player, card, event) ){
         return can_target(&td) | can_target(&td2);
      }
   }

   if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
      instance->number_of_targets = 0;
      int i;
      if( ! check_special_flags2(player, card, SF2_COPIED_FROM_STACK) ){
         instance->info_slot = 0;
         while( (instance->info_slot & CHOICE_MASK) != CHOICE_MASK ){
               int can_untap = !(instance->info_slot & CHOICE_UNTAP) && can_target(&td) ? 1 : 0;
               int can_target_opp = !(instance->info_slot & CHOICE_OPP_SAC) && would_validate_arbitrary_target(&td2, 1-player, -1) ? 1 : 0;
               if( player == AI && ! new_can_sacrifice(player, card, 1-player, &test) ){
                  can_target_opp = 0;
               }
               int choice = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_RANDOM,
                              "Target player gains 4 life", !(instance->info_slot & CHOICE_GAIN_LIFE), 5,
                              "Untap up to two target creatures", can_untap, 10,
                              "Target opponent sacrifices an attacking creature", can_target_opp, 15);

               if( ! choice ){
                  spell_fizzled = 1;
                  return 0;
               }
               // Convert choice number 1, 2, 3 in choice bits 001, 010, 100
               instance->info_slot |= 1<<(choice-1);
               // The second condition prevents a third escalation
               if( has_mana(player, COLOR_ANY, 2) && ((instance->info_slot & CHOICE_MASK) != CHOICE_MASK) ){
                  int choice2 = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_NO_CANCEL,
                                 "Escalate", 1, 10,
                                 "Decline", 1, 1);
                  if( choice2 == 1 ){
                     charge_mana(player, COLOR_ANY, 2);
                     if( spell_fizzled == 1 ){
                        spell_fizzled = 0;
                        break;
                     }
                  }
                  else{
                     break;
                  }
               }
               else{
                  break;
               }
         }
      }
      if( instance->info_slot & CHOICE_GAIN_LIFE ){
         if( new_pick_target(&td2, "TARGET_PLAYER", -1, 1) ){
               // If gain life has been chosen put the chosen player id in targets[0]
            // new_pick_target incremented number_of_targets
            }
            else{
               return 0;
            }
         }
         if( instance->info_slot & CHOICE_UNTAP ){
           for(i=0; i<2; i++){
               if( new_pick_target(&td, "TARGET_CREATURE", -1, 0) ){
                  // If untap has been chosen put one or two target creatures in targets[0] and targets[1] OR
                  // targets[1] and targets[2] if gain life has also been chosen
         // new_pick_target incremented number_of_targets
                  if ( i == 0) {
                     // Only the first target must be untargettable
                     state_untargettable(instance->targets[instance->number_of_targets-1].player, \
                     instance->targets[instance->number_of_targets-1].card, 1);
               }
               }
               else{
                  break;
               }
           }
           // target[0] is set if gain life has been chosen
           int first_target_picked = (instance->info_slot & CHOICE_GAIN_LIFE) ? 1 : 0 ;
         state_untargettable(instance->targets[first_target_picked].player, instance->targets[first_target_picked].card, 0);
      }
      if( instance->info_slot & CHOICE_OPP_SAC ){
         // If opponent sacs has been chosen put opponent into
         //   targets[0] if this was the only choice
         //   targets[1] if gain life has been chosen
         //   targets[2] or targets[3] if untap has been chosen, depending on whether one or two creatures untap
         instance->targets[instance->number_of_targets].player = 1-player;
         instance->targets[instance->number_of_targets].card = -1;
         instance->number_of_targets++;
      }
   }

   if( event == EVENT_RESOLVE_SPELL ){
      int targets_found = 0;
        if( instance->info_slot & CHOICE_GAIN_LIFE ){
            // If gain life has been chosen the chosen player id is in targets[0]
           if( validate_target(player, card, &td2, targets_found) ){
               gain_life(instance->targets[targets_found].player, 4);
           }
           targets_found++;
      }
      if( instance->info_slot & CHOICE_UNTAP ){
         // If untap has been chosen target creature(s) is/are in targets[targets_found] (and targets[targets_found+1])
           int i;
           // target[0] is set if gain life has been chosen
           int first_target_picked = (instance->info_slot & CHOICE_GAIN_LIFE) ? 1 : 0 ;
           for(i=first_target_picked; i<instance->number_of_targets; i++){
              // If card == -1 there are no more targets to untap (but maybe opponent must sac an attacker)
              if (instance->targets[i].card != -1) {
                  if( validate_target(player, card, &td, i) ){
                   untap_card(instance->targets[i].player, instance->targets[i].card);
               }
               targets_found++;
              }
            }
         }
         if( instance->info_slot & CHOICE_OPP_SAC ){
            // The opponent as target is always in the last slot
           if( validate_target(player, card, &td2, targets_found) ){
               new_sacrifice(player, card, 1-player, SAC_NO_CANCEL | SAC_CAUSED, &test);
            }
         }
       kill_card(player, card, KILL_DESTROY);
   }

   return 0;
}
As for testing, you want to look at the following cases: option 1, 2, 3 each stand alone, options 1+2, 1+3, 2+3 and 1+2+3 (because options are always worked off in the same order the order of choice doesn't matter). Make sure you test #2 with one and two creatures as this messed up the original book keeping.
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 194
Joined: 24 Dec 2019, 10:59
Has thanked: 13 times
Been thanked: 16 times

Re: [confirmed]Blessed Alliance

Postby Korath » 13 Sep 2020, 19:11

You'll also need to test with zero creatures chosen in each case with option 2, which is legal (though never advantageous unless mana burn is enabled). Similarly, option 2 should always be enabled even if there's no legal targets, just discouraged for the AI.
User avatar
Korath
DEVELOPER
 
Posts: 3456
Joined: 02 Jun 2013, 05:57
Has thanked: 488 times
Been thanked: 976 times

Re: [confirmed]Blessed Alliance

Postby Aswan jaguar » 14 Sep 2020, 12:19

With above code:
Blessed Alliance 2nd ability can't be chosen with no creatures on battlefield and if there are creatures but you cancel first target (so 0 targets) it errors (normal & in escalate), it shouldn't.
| Open
bad parameters
get_card_instance(-1, -1)
0: 0x0254A0F8
1: 0x024FFA0E
2: 0x0251B6BC
3: 0x023CC907
4: 0x0255A375
5: 0x0253886D
6: 0x025393AE
7: 0x00435A13
8: 0x00433AF9
9: 0x0043BF12
10: 0x004399BD
11: 0x0047902C
12: 0x004946E9
13: 0x7C80B729
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 7213
Joined: 13 May 2010, 12:17
Has thanked: 596 times
Been thanked: 327 times

Re: [confirmed]Blessed Alliance

Postby FastEddie » 14 Sep 2020, 15:40

At your service, gentlemen.

This should fix all open issues.
No crash anymore when cancelling the second option without choosing a creature.
The second option is available with no creatures in play.

Korath, I hope I understood this right. DIALOG uses priority 10 if a targetable creature is in play (as before), but 1 in case none is (lowest value among the three).

Let me know what you think.

Code: Select all
int card_blessed_alliance(int player, int card, event_t event){
 /* Blessed Alliance   |1|W   0x200ef08
  * Instant
  * Escalate |2
  * Choose one or more -
  * * Target player gains 4 life.
  * * Untap up to two target creatures.
  * * Target opponent sacrifices an attacking creature. */

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

   enum
   {
      CHOICE_GAIN_LIFE    = 1<<0,
      CHOICE_UNTAP       = 1<<1,
      CHOICE_OPP_SAC       = 1<<2,
      CHOICE_MASK = CHOICE_GAIN_LIFE | CHOICE_UNTAP | CHOICE_OPP_SAC,
   };

   target_definition_t td;
   default_target_definition(player, card, &td, TYPE_CREATURE );
   td.preferred_controller = player;

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

   test_definition_t test;
   default_test_definition(&test, TYPE_CREATURE);
   test.state = STATE_ATTACKING;

   card_instance_t *instance = get_card_instance(player, card);

   if( event == EVENT_CAN_CAST ){
      if( basic_spell(player, card, event) ){
         return can_target(&td) | can_target(&td2);
      }
   }

   if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
      instance->number_of_targets = 0;
      int i;
      if( ! check_special_flags2(player, card, SF2_COPIED_FROM_STACK) ){
         instance->info_slot = 0;
         while( (instance->info_slot & CHOICE_MASK) != CHOICE_MASK ){
               int can_untap = !(instance->info_slot & CHOICE_UNTAP) ? 1 : 0;
               int can_target_opp = !(instance->info_slot & CHOICE_OPP_SAC) && would_validate_arbitrary_target(&td2, 1-player, -1) ? 1 : 0;
               if( player == AI && ! new_can_sacrifice(player, card, 1-player, &test) ){
                  can_target_opp = 0;
               }
               int choice = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_RANDOM,
                              "Target player gains 4 life", !(instance->info_slot & CHOICE_GAIN_LIFE), 5,
                              "Untap up to two target creatures", can_untap, can_target(&td) ? 10 : 1,
                              "Target opponent sacrifices an attacking creature", can_target_opp, 15);

               if( ! choice ){
                  spell_fizzled = 1;
                  return 0;
               }
               // Convert choice number 1, 2, 3 in choice bits 001, 010, 100
               instance->info_slot |= 1<<(choice-1);
               // The second condition prevents a third escalation
               if( has_mana(player, COLOR_ANY, 2) && ((instance->info_slot & CHOICE_MASK) != CHOICE_MASK) ){
                  int choice2 = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_NO_CANCEL,
                                 "Escalate", 1, 10,
                                 "Decline", 1, 1);
                  if( choice2 == 1 ){
                     charge_mana(player, COLOR_ANY, 2);
                     if( spell_fizzled == 1 ){
                        spell_fizzled = 0;
                        break;
                     }
                  }
                  else{
                     break;
                  }
               }
               else{
                  break;
               }
         }
      }
      if( instance->info_slot & CHOICE_GAIN_LIFE ){
         if( new_pick_target(&td2, "TARGET_PLAYER", -1, 1) ){
              // If gain life has been chosen put the chosen player id in targets[0]
            // new_pick_target incremented number_of_targets
           }
           else{
             return 0;
          }
      }
      if( instance->info_slot & CHOICE_UNTAP ){
         int creatures_selected = 0;
          for(i=0; i<2; i++){
              if( new_pick_target(&td, "TARGET_CREATURE", -1, 0) ){
                  // If untap has been chosen put one or two target creatures in targets[0] and targets[1] OR
                 // targets[1] and targets[2] if gain life has also been chosen
               // new_pick_target incremented number_of_targets
               creatures_selected++;
                 if ( i == 0) {
                    // Only the first target must be untargettable
                  state_untargettable(instance->targets[instance->number_of_targets-1].player, \
                     instance->targets[instance->number_of_targets-1].card, 1);
               }
              }
              else{
                 break;
              }
          }
          if ( creatures_selected != 0) {
             // target[0] is set if gain life has been chosen
             int first_target_picked = (instance->info_slot & CHOICE_GAIN_LIFE) ? 1 : 0 ;
            state_untargettable(instance->targets[first_target_picked].player, instance->targets[first_target_picked].card, 0);
          }
      }
      if( instance->info_slot & CHOICE_OPP_SAC ){
         // If opponent sacs has been chosen put opponent into
         //   targets[0] if this was the only choice
         //   targets[1] if gain life has been chosen or zero creatures untap
         //   targets[2] or targets[3] if untap has been chosen, depending on whether one or two creatures untap
         instance->targets[instance->number_of_targets].player = 1-player;
         instance->targets[instance->number_of_targets].card = -1;
         instance->number_of_targets++;
      }
   }

   if( event == EVENT_RESOLVE_SPELL ){
      int targets_found = 0;
        if( instance->info_slot & CHOICE_GAIN_LIFE ){
            // If gain life has been chosen the chosen player id is in targets[0]
           if( validate_target(player, card, &td2, targets_found) ){
               gain_life(instance->targets[targets_found].player, 4);
           }
           targets_found++;
      }
      if( instance->info_slot & CHOICE_UNTAP ){
         // If untap has been chosen target creature(s) is/are in targets[targets_found] (and targets[targets_found+1])
           int i;
           // target[0] is set if gain life has been chosen
           int first_target_picked = (instance->info_slot & CHOICE_GAIN_LIFE) ? 1 : 0 ;
           for(i=first_target_picked; i<instance->number_of_targets; i++){
              // If card == -1 there are no more targets to untap (but maybe opponent must sac an attacker)
              if (instance->targets[i].card != -1) {
                  if( validate_target(player, card, &td, i) ){
                   untap_card(instance->targets[i].player, instance->targets[i].card);
               }
               targets_found++;
              }
            }
         }
         if( instance->info_slot & CHOICE_OPP_SAC ){
            // The opponent as target is always in the last slot
           if( validate_target(player, card, &td2, targets_found) ){
               new_sacrifice(player, card, 1-player, SAC_NO_CANCEL | SAC_CAUSED, &test);
            }
         }
       kill_card(player, card, KILL_DESTROY);
   }

   return 0;
}
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 194
Joined: 24 Dec 2019, 10:59
Has thanked: 13 times
Been thanked: 16 times

Re: [confirmed]Blessed Alliance

Postby Aswan jaguar » 14 Sep 2020, 19:06

This last time I tested only the last 2 issues and not everything else again, and those are fine now as you know. It has a really small issue that can be demonstrated if both player's have shroud then you can't cast Blessed Alliance at all, at least if no creature is in play. I tried a little to fix this last issue but then other bugs come up so I stopped as I really don't want to test this anymore :-& . Either you fix this last thing or not you should commit this.
---
Trying to squash some bugs and playtesting.
User avatar
Aswan jaguar
Super Tester Elite
 
Posts: 7213
Joined: 13 May 2010, 12:17
Has thanked: 596 times
Been thanked: 327 times

Re: [confirmed]Blessed Alliance

Postby Korath » 14 Sep 2020, 19:46

Well, without having looked at any other part of it, that's easy to fix - just always return 1 for EVENT_CAN_CAST (since you can always cast with zero targets, by choosing only mode 2). I don't see immediately see anything else checking to see if there's a legal target available for mode 1, though.

An aside - that choice text looks awful long. Does it really fit in the dialog? It may be worth looking into backporting Shandalar's do_dialog() rewrite from ui.cpp, in the "do_dialog(), retooled choose_a_number()" > "main do_dialog()" > "middle/back-end" fold; besides wrapping text, it also lets you use mana symbols. It won't be a quick or easy project, though.
User avatar
Korath
DEVELOPER
 
Posts: 3456
Joined: 02 Jun 2013, 05:57
Has thanked: 488 times
Been thanked: 976 times

Re: [confirmed]Blessed Alliance

Postby FastEddie » 15 Sep 2020, 11:14

Before I start... right now EVENT_CAN_CAST looks like this:
Code: Select all
if( event == EVENT_CAN_CAST ){
      if( basic_spell(player, card, event) ){
         return can_target(&td) | can_target(&td2);
      }
   }
I would change it like this:
Code: Select all
if( event == EVENT_CAN_CAST ){
      if( basic_spell(player, card, event) ){
         return 1;
      }
   }
Question: do we still need the basic_spell and if so, what is it good for?
Korath wrote:I don't see immediately see anything else checking to see if there's a legal target available for mode 1, though.
There are currently no checks. Would it be correct to check whether PB_PLAYER_HAS_SHROUD is set for both players?
Korath wrote:An aside - that choice text looks awful long.
This is correct, it doesn't fit the box properly. I saw it but didn't see it... will fix that and since I was also looking for a do_dialog that admits line breaks I put your proposal on my evergrowing list of things to look into.

Aswan Jaguar, thanks for your continued support and testing! I know this is tedious but I am optimistic that we are pretty much at the end.
As an aside, I guess all other cards with the escalate mechanic have the issue that you can escalate n times where n is the number of choices while it should be n-1 (Borrowed Grace has the same issue and I bet this was copy and paste). I will fix this next unless you want to have a look. This should be pretty straightforward I think (famous last words!).
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 194
Joined: 24 Dec 2019, 10:59
Has thanked: 13 times
Been thanked: 16 times

Re: [confirmed]Blessed Alliance

Postby Korath » 15 Sep 2020, 13:28

FastEddie wrote:Question: do we still need the basic_spell and if so, what is it good for?
All it does is return 1 if event == EVENT_CAN_CAST || event == EVENT_CAN_CHANGE_TARGET, and engage in undefined behavior in the latter case if you've put non-target data in targets[0].card. Very, very verbosely.
FastEddie wrote:Would it be correct to check whether PB_PLAYER_HAS_SHROUD is set for both players?
There's plenty of things that can make a player untargetable - your opponent gaining hexproof from something like Aegis of the Gods, or either player gaining protection from Seht's Tiger or Faith's Shield or Teferi's Protection, for example. Just move the can_target(&td2) that you had in EVENT_CAN_CAST into the first mode's legality check.
User avatar
Korath
DEVELOPER
 
Posts: 3456
Joined: 02 Jun 2013, 05:57
Has thanked: 488 times
Been thanked: 976 times

Re: [confirmed]Blessed Alliance

Postby FastEddie » 16 Sep 2020, 09:43

Alright... that should settle all currently open issues.
I attached a bunch of save games I used for testing for your convenience (with Ivory Mask as shroud effect for each player and also both).

Korath wrote:All it does is return 1 if event == EVENT_CAN_CAST || event == EVENT_CAN_CHANGE_TARGET, and engage in undefined behavior in the latter case if you've put non-target data in targets[0].card.
Which sounds pretty redundant... is this function still needed or could it be deprecated?

Korath wrote:Just move the can_target(&td2) that you had in EVENT_CAN_CAST into the first mode's legality check.
Thanks. I didn't realize the player bit is narrower... the whole testing machine still feels a bit unnatural to me.

Code: Select all
int card_blessed_alliance(int player, int card, event_t event){
 /* Blessed Alliance   |1|W   0x200ef08
  * Instant
  * Escalate |2
  * Choose one or more -
  * * Target player gains 4 life.
  * * Untap up to two target creatures.
  * * Target opponent sacrifices an attacking creature. */

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

   enum
   {
      CHOICE_GAIN_LIFE    = 1<<0,
      CHOICE_UNTAP       = 1<<1,
      CHOICE_OPP_SAC       = 1<<2,
      CHOICE_MASK = CHOICE_GAIN_LIFE | CHOICE_UNTAP | CHOICE_OPP_SAC,
   };

   target_definition_t td_creature;
   default_target_definition(player, card, &td_creature, TYPE_CREATURE );
   td_creature.preferred_controller = player;

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

   target_definition_t td_opp;
   default_target_definition(1-player, card, &td_opp, 0);
   td_opp.zone = TARGET_ZONE_PLAYERS;

   test_definition_t test;
   default_test_definition(&test, TYPE_CREATURE);
   test.state = STATE_ATTACKING;

   card_instance_t *instance = get_card_instance(player, card);

      if( event == EVENT_CAN_CAST ){
      return 1;
      }

      if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
      instance->number_of_targets = 0;
         int remaining_choices = 3;
         int can_target_any_player = (can_target(&td_player) || can_target(&td_opp));
         if ( !can_target_any_player ) {
            remaining_choices--;
         }
         int can_target_opp_creature = would_validate_arbitrary_target(&td_player, 1-player, -1);
         if ( !can_target_opp_creature ) {
            remaining_choices--;
         }
         if( ! check_special_flags2(player, card, SF2_COPIED_FROM_STACK) ){
            instance->info_slot = 0;
            while( ((instance->info_slot & CHOICE_MASK) != CHOICE_MASK) && (remaining_choices > 0) ){
               int can_gain = !(instance->info_slot & CHOICE_GAIN_LIFE) && can_target_any_player;
                  int can_untap = !(instance->info_slot & CHOICE_UNTAP);
                  int can_target_opp_attacker = !(instance->info_slot & CHOICE_OPP_SAC) && can_target_opp_creature;
                  if( player == AI && ! new_can_sacrifice(player, card, 1-player, &test) ){
                     can_target_opp_attacker = 0;
                  }
            int choice = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_RANDOM,
                                 "Target player gains 4 life", can_gain, 5,
                                 "Untap up to 2 creatures", can_untap, can_target(&td_creature) ? 10 : 1,
                                 "Opponent sacs an attacker", can_target_opp_attacker, 15);

                  if( !choice ){
               spell_fizzled = 1;
                     return 0;
                  }
                  remaining_choices--;
                  // Convert choice number 1, 2, 3 in choice bits 001, 010, 100
                  instance->info_slot |= 1<<(choice-1);
                  // The second condition prevents a third escalation
                  if( has_mana(player, COLOR_ANY, 2) && (remaining_choices > 0) ){
                  int choice2 = DIALOG(player, card, event, DLG_NO_STORAGE, DLG_NO_CANCEL,
                                 "Escalate", 1, 10,
                                 "Decline", 1, 1);
                     if( choice2 == 1 ){
                       charge_mana(player, COLOR_ANY, 2);
                        if( spell_fizzled == 1 ){
                           spell_fizzled = 0;
                           break;
                        }
                     }
                     else{
                       break;
                     }
                  }
                  else{
               break;
                  }
            }
         }
      if( instance->info_slot & CHOICE_GAIN_LIFE ){
         if( new_pick_target(&td_player, "TARGET_PLAYER", -1, 1) ){
              // If gain life has been chosen put the chosen player id in targets[0]
            // new_pick_target incremented number_of_targets
           }
           else{
             return 0;
          }
      }
      if( instance->info_slot & CHOICE_UNTAP ){
         int i, creatures_selected = 0;
          for(i=0; i<2; i++){
              if( new_pick_target(&td_creature, "TARGET_CREATURE", -1, 0) ){
                  // If untap has been chosen put one or two target creatures in targets[0] and targets[1] OR
                 // targets[1] and targets[2] if gain life has also been chosen
               // new_pick_target incremented number_of_targets
               creatures_selected++;
                 if ( i == 0) {
                    // Only the first target must be untargettable
                  state_untargettable(instance->targets[instance->number_of_targets-1].player, \
                     instance->targets[instance->number_of_targets-1].card, 1);
               }
              }
              else{
                 break;
              }
          }
          if ( creatures_selected != 0) {
             // target[0] is set if gain life has been chosen
             int first_target_picked = (instance->info_slot & CHOICE_GAIN_LIFE) ? 1 : 0 ;
            state_untargettable(instance->targets[first_target_picked].player, instance->targets[first_target_picked].card, 0);
          }
      }
      if( instance->info_slot & CHOICE_OPP_SAC ){
         // If opponent sacs has been chosen put opponent into
         //   targets[0] if this was the only choice
         //   targets[1] if gain life has been chosen or zero creatures untap
         //   targets[2] or targets[3] if untap has been chosen, depending on whether one or two creatures untap
         instance->targets[instance->number_of_targets].player = 1-player;
         instance->targets[instance->number_of_targets].card = -1;
         instance->number_of_targets++;
      }
   }

   if( event == EVENT_RESOLVE_SPELL ){
      int targets_found = 0;
        if( instance->info_slot & CHOICE_GAIN_LIFE ){
            // If gain life has been chosen the chosen player id is in targets[0]
           if( validate_target(player, card, &td_player, targets_found) ){
               gain_life(instance->targets[targets_found].player, 4);
           }
           targets_found++;
      }
      if( instance->info_slot & CHOICE_UNTAP ){
         // If untap has been chosen target creature(s) is/are in targets[targets_found] (and targets[targets_found+1])
           int i;
           // target[0] is set if gain life has been chosen
           int first_target_picked = (instance->info_slot & CHOICE_GAIN_LIFE) ? 1 : 0 ;
           for(i=first_target_picked; i<instance->number_of_targets; i++){
              // If card == -1 there are no more targets to untap (but maybe opponent must sac an attacker)
              if (instance->targets[i].card != -1) {
                  if( validate_target(player, card, &td_creature, i) ){
                   untap_card(instance->targets[i].player, instance->targets[i].card);
               }
               targets_found++;
              }
            }
         }
         if( instance->info_slot & CHOICE_OPP_SAC ){
            // The opponent as target is always in the last slot
           if( validate_target(player, card, &td_player, targets_found) ){
               new_sacrifice(player, card, 1-player, SAC_NO_CANCEL | SAC_CAUSED, &test);
            }
         }
       kill_card(player, card, KILL_DESTROY);
   }

   return 0;
}
Attachments
BlessedAlliance.zip
(15.16 KiB) Downloaded 3 times
---
Argivian Archaeologist in the Library of Leng studying the Spells of the Ancients
User avatar
FastEddie
 
Posts: 194
Joined: 24 Dec 2019, 10:59
Has thanked: 13 times
Been thanked: 16 times

Next

Return to Pending Reports

Who is online

Users browsing this forum: No registered users and 2 guests


Who is online

In total there are 2 users online :: 0 registered, 0 hidden and 2 guests (based on users active over the past 10 minutes)
Most users ever online was 1371 on 09 Feb 2020, 16:22

Users browsing this forum: No registered users and 2 guests

Login Form