Page 1 of 1

[conf]Karn Liberated restart game ability not begining turn

PostPosted: 15 Mar 2021, 15:52
by Aswan jaguar
Describe the Bug:
Karn Liberated 's restart game ability doesn't begin the turn from the beginning but from the step it was activated so creatures or lands that were exiled by it's abilities like Akum Refuge don't untap and creatures have summoning sickness while they shouldn't.

Which card did behave improperly?
Karn Liberated

Which update are you using? (date, name)Which type? (duel, gauntlet, sealed deck)
March 2021 - Allegiances and Alliances, duel

What exactly should be the correct behavior/interaction?
Relevant rules:
8/7/2020 Permanents put onto the battlefield due to Karn’s ability will have been under the starting controller’s control continuously since the beginning of that player’s first turn. Creatures among them can attack and their activated abilities with Tap in the cost can be activated.
8/7/2020 Any permanents put onto the battlefield with Karn’s ability that entered the battlefield tapped will untap during their controller’s first untap step.

Are any other cards possibly affected by this bug?
-

Re: Karn Liberated restart game ability not begining turn

PostPosted: 16 Mar 2021, 05:27
by drool66
It was also shuffling the Rules Engine into the deck - I've now fixed that and reset some global variables to their initial values, but not the current_phase.

Re: [conf]Karn Liberated restart game ability not begining t

PostPosted: 27 Mar 2021, 22:02
by drool66
I managed to find a way to do it, albeit weirdly. I can only get the card to take the player to the precombat main phase, but I can automate everything (I think) before that. Weird thing is that all of the decisions are made for them - if they have mana to pay upkeep costs they are paid automatically, for example. There's probably more code that can be inserted, but this is still an upgrade from what we have:
Code: Select all
void clear_all_traps(int player);
int untap_phase(int player);
int upkeep_phase(int player);
void phase_changed(int player, int new_phase);
int card_karn_liberated(int player, int card, event_t event){

   /* Karn Liberated
    * |7
    * Planeswalker - Karn (6)
    * +4: Target player exiles a card from his or her hand.
    * -3: Exile target permanent.
    * -14: Restart the game, leaving in exile all non-Aura permanent cards exiled with ~. Then put those cards onto the battlefield under your control. */

   check_legend_rule(player, card, event);

   if (IS_ACTIVATING(event)){

      card_instance_t* instance = get_card_instance(player, card);

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

      target_definition_t td1;
      default_target_definition(player, card, &td1, TYPE_PERMANENT);

      enum{
         CHOICE_EXILE_CARD_IN_HAND = 1,
         CHOICE_EXILE_PERMANENT,
         CHOICE_RESTART_GAME,
      };

      int choice = DIALOG(player, card, event, DLG_RANDOM, DLG_PLANESWALKER,
                     "Exile a card in target's hand", can_target(&td), would_validate_arbitrary_target(&td, 1-player, -1) && hand_count[1-player] ? 15 : 0, 4,
                     "Exile a permanent", can_target(&td1), 10, -3,
                     "Restart the game", 1, 20, -14);

     if (event == EVENT_CAN_ACTIVATE)
      {
        if (!choice)
         return 0;
      }
     else if (event == EVENT_ACTIVATE)
      {
        instance->number_of_targets = 0;
        switch (choice)
         {
            case CHOICE_EXILE_CARD_IN_HAND:
               pick_target(&td, "TARGET_PLAYER");
               break;

            case CHOICE_EXILE_PERMANENT:
               pick_target(&td1, "TARGET_PERMANENT");
               break;

            case CHOICE_RESTART_GAME:
            {
               instance->targets[1].card = -1;
               if( count_counters(player, card, COUNTER_LOYALTY) <= 14 || instance->kill_code ){
                  instance->targets[1].card = exiledby_detach(player, card);
               }
               break;
            }
         }
      }
     else   // event == EVENT_RESOLVE_ACTIVATION
      switch (choice)
        {
         case CHOICE_EXILE_CARD_IN_HAND:
            {
               if( valid_target(&td)){
                  if( hand_count[instance->targets[0].player] ){
                     int selected = -1;
                     test_definition_t this_test;
                     if( instance->targets[0].player == HUMAN ){
                        default_test_definition(&this_test, TYPE_ANY);
                        selected = new_select_a_card(instance->targets[0].player, instance->targets[0].player, TUTOR_FROM_HAND, 1, AI_MIN_VALUE, -1, &this_test);
                     }
                     else{
                        default_test_definition(&this_test, TYPE_PERMANENT);
                        this_test.type_flag = DOESNT_MATCH;
                        selected = new_select_a_card(instance->targets[0].player, instance->targets[0].player, TUTOR_FROM_HAND, 1, AI_MIN_VALUE, -1, &this_test);
                        if( selected == -1 ){
                           default_test_definition(&this_test, TYPE_ANY);
                           selected = new_select_a_card(instance->targets[0].player, instance->targets[0].player, TUTOR_FROM_HAND, 1, AI_MIN_VALUE, -1, &this_test);
                        }
                     }
                     if( in_play(instance->parent_controller, instance->parent_card) ){
                        exile_card_and_remember_it_on_exiledby(instance->parent_controller, instance->parent_card,
                                                      instance->targets[0].player, selected);
                     }
                     else{
                        rfg_card_in_hand(instance->targets[0].player, instance->targets[0].card);
                     }
                  }
               }
            }
            break;

         case CHOICE_EXILE_PERMANENT:
            {
               if( valid_target(&td1)){
                  if( ! is_token(instance->targets[0].player, instance->targets[0].card) &&
                     in_play(instance->parent_controller, instance->parent_card) )
                  {
                     exile_card_and_remember_it_on_exiledby(instance->parent_controller, instance->parent_card,
                                                   instance->targets[0].player, instance->targets[0].card);
                  }
                  else{
                     kill_card(instance->targets[0].player, instance->targets[0].card, KILL_EXILE);
                  }
               }
            }
            break;

         case CHOICE_RESTART_GAME:
            {
               int exby_player = instance->targets[1].card > -1 ? player-2 : instance->parent_controller;
               int exby_card = instance->targets[1].card > -1 ? instance->targets[1].card : instance->parent_card;
               int dead = instance->targets[1].card > -1;
               int exiled_by_karn[2][18];
               memset(exiled_by_karn, -1, sizeof(exiled_by_karn));
               int exc[2] = {0, 0};
               int leg = 0;
               int idx = 0;
               int* loc;
               while ((loc = exiledby_find_any(exby_player, exby_card, &leg, &idx)) != NULL){
                     int owner = (*loc & 0x80000000) ? 1 : 0;
                     int iid = *loc & ~0x80000000;
                     *loc = -1;
                     exiled_by_karn[owner][exc[owner]] = iid;
                     exc[owner]++;
               }
               // approximation for "restart the game"
               APNAP(p,{
                        int c;
                        for(c=active_cards_count[p]-1; c>-1; c--){
                           if( in_play(p, c) && is_what(p, c, TYPE_ENCHANTMENT) && has_subtype(p, c, SUBTYPE_AURA) ){
                              put_on_top_of_deck(p, c);
                           }
                        }
                     };
               );
               APNAP(p,{
                        int c;
                        for(c=active_cards_count[p]-1; c>-1; c--){
                           if( in_hand(p, c) ){
                              put_on_top_of_deck(p, c);
                           }
                           if( in_play(p, c) ){
                              int id = get_id(p, c), iid = get_original_internal_card_id(p, c);
                              if( is_what(p, c, TYPE_EFFECT) && iid != activation_card &&
                                    cards_data[iid].cc[2] != 99 && cards_data[iid].cc[2] != 3 &&
                                    id != 916 && id != CARD_ID_RULES_ENGINE && id != CARD_ID_DEADBOX
                              ){
                                 kill_card(p, c, KILL_EXILE);
                              }
                              if( is_what(p, c, TYPE_PERMANENT) ){
                                 put_on_top_of_deck(p, c);
                              }
                           }
                        }
                     };
               );
               APNAP(p,{
                          reshuffle_grave_into_deck(p, 1);
                      };
               );
               APNAP(p,{
                        life[p] = get_starting_life_total(p);
                        raw_set_poison(p, 0);
                        raw_set_experience(p, 0);
                        player_counters[player].energy_ = 0;
                     };
               );
               APNAP(p,{
                        int i;
                        for(i=0; i<exc[p]; i++){
                           if( ! is_what(-1, exiled_by_karn[p][i], TYPE_PERMANENT) || has_subtype_by_id(cards_data[exiled_by_karn[p][i]].id, SUBTYPE_AURA) ){
                              remove_card_from_rfg(p, cards_data[exiled_by_karn[p][i]].id);
                              raw_put_iid_on_top_of_deck(p, exiled_by_karn[p][i]);
                              exiled_by_karn[p][i] = -1;
                           }
                        }
                     };
               );
               APNAP(p,{
                        shuffle(p);
                        draw_cards(p, 7);
                     };
               );
               APNAP(p,{
                        int i;
                        for(i=0; i<exc[p]; i++){
                           if( exiled_by_karn[p][i] > -1 ){
                              int card_added = add_card_to_hand(player, exiled_by_karn[p][i]);
                              if( p != player ){
                                 if( player == AI ){
                                    remove_state(player, card_added, STATE_OWNED_BY_OPPONENT);
                                 }
                                 else{
                                    add_state(player, card_added, STATE_OWNED_BY_OPPONENT);
                                 }
                              }
                              remove_card_from_rfg(p, cards_data[exiled_by_karn[p][i]].id);
                              exiled_by_karn[p][i] = -1;
                              put_into_play(player, card_added);
                              /* 8/7/2020 Permanents put onto the battlefield due to Karn's ability will have been under the starting controller's control continuously since the beginning of that player's first turn. Creatures among them can attack and their activated abilities with Tap in the cost can be activated.
                              *  8/7/2020 Any permanents put onto the battlefield with Karn's ability that entered the battlefield tapped will untap during their controller's first untap step. */
                              if(is_what(player, card_added, TYPE_PERMANENT)){
                                 remove_summoning_sickness(player, card_added);
                                 remove_state(player, card_added, STATE_TAPPED);
                              }
                           }
                        }
                     };
               );
               if( dead )
                  exiledby_destroy_detached(player, instance->targets[1].card);

               memset(cards_drawn_this_turn, 0, sizeof(cards_drawn_this_turn));
               memset(mana_pool, 0, sizeof(mana_pool));
               turn_count = 0;
               lands_played = 0;
               APNAP(p, {clear_all_traps(p);};);
               land_can_be_played = 0;
               recalculate_all_cards_in_play();
               current_turn = player;
               untap_phase(player);
               upkeep_phase(player);
               phase_changed(player, PHASE_DRAW);
               dispatch_event(player, card, EVENT_DRAW_PHASE);
               phase_changed(player, PHASE_MAIN1);
               current_phase = PHASE_MAIN1;
            }
            break;
        }
   }

   return planeswalker(player, card, event, 6);
}

Re: [conf]Karn Liberated restart game ability not begining t

PostPosted: 31 Mar 2021, 05:35
by Aswan jaguar
I have run few tests and I agree this is better from what we have now.

Re: [conf]Karn Liberated restart game ability not begining t

PostPosted: 01 Apr 2021, 20:21
by Korath
I haven't looked at what kind of hackery you added to implement Time Stop, but maybe reuse that as an alternative?

Re: [conf]Karn Liberated restart game ability not begining t

PostPosted: 01 Apr 2021, 22:11
by drool66
Oh there's plenty of hackery there, but at its heart it just empties the stack and moves to PHASE_DISCARD, disabling everything until it can get there. I was thinking about that - a special end the turn legacy that also includes a time walk (if current_turn == player), and does everything Karn does at PHASE_START, or failing that, PHASE_UNTAP. I'll give it a shot.