Page 1 of 2

[fixed]Selhoff Occultist dying simultaneously problems

PostPosted: 20 Feb 2017, 20:00
by Aswan jaguar
Describe the Bug:
1- If Selhoff Occultist kills and dies in a battle you get only 1 trigger and one prompt however correctly 2 cards go to gy from targeted players library. It works fine if multiple creatures die, but not if also both Selhoff Occultist and creature it blocks/is blocked die at the same time.

2- If a card like Earthquake kills Selhoff Occultist and other creatures then you get only 1 trigger and 1 card goes to graveyard from targeted player.The same happens if it dies by first strike.No matter how many creatures die also from first strike and normal damage it will trigger only once and only 1 card will go to graveyard.

Which card did behave improperly?
Selhoff Occultist

Which update are you using? (date, name)Which type? (duel, gauntlet, sealed deck)
Manalink 2016/08/27: Eldritch Moon v2, duel

What exactly should be the correct behavior/interaction?
Selhoff Occultist if it kills and dies in a battle you get that many triggers and that many prompts to select target player one for each creature that died.

Are any other cards possibly affected by this bug?
Other trigger on dying cards?

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 18 Mar 2021, 01:39
by drool66
I believe 2 is fixed locally (I'll push as soon as I can reach the server), but not 1, which seems very strange. I can't find a card that handles 1 properly.

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 18 Mar 2021, 22:51
by drool66
So the issue is something with the dispatch of TRIGGER_GRAVEYARD_FROM_PLAY. I can get the proper behavior by dispatching it in the legacy during EVENT_GRAVEYARD_FROM_PLAY if affect_me(p, c), where p, c are Selhoff Occultist. I don't have the resources to see if this is a bug or not, but I do know it doesn't affect how other cards checking for TRIGGER_GRAVEYARD_FROM_PLAY work, which is a huge relief.

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 19 Mar 2021, 06:23
by Aswan jaguar
Adding some more info I found and worked upon:
This code handles all the problems as long as it is it's controller's turn not tested for multiple Selhoff Occultist in play tested this works, too:
Code: Select all
static int selhoff_occultist_legacy(int player, int card, event_t event){

   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
      card_instance_t *instance = get_card_instance(player, card);
      if( instance->damage_target_player > -1 ){
         if( get_id(instance->damage_target_player, instance->damage_target_card) != CARD_ID_SELHOFF_OCCULTIST ){
            kill_card(player, card, KILL_EXILE);
         }
      }
   }

   if( get_card_instance(player, card)->damage_target_player > -1 && effect_follows_control_of_attachment(player, card, event) ){
      return 0;
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
      card_instance_t *instance = get_card_instance(player, card);
      if( in_play(affected_card_controller, affected_card) ){
         int kill_code = get_card_instance(affected_card_controller, affected_card)->kill_code;
         if( kill_code ){
            if( instance->damage_target_player > -1 ){
               int p = instance->damage_target_player;
               int c = instance->damage_target_card;
               if( affect_me(p, c) ){
                  if( is_this_dying(p, c) ){
                     SET_BYTE1(instance->targets[11].player)++;
                  if( instance->targets[11].player < 0 ){
                     instance->targets[11].player = 0;
                  }
                  SET_BYTE1(instance->targets[11].player) = 1;
                  }
                  instance->damage_target_player = instance->damage_target_card = -1;
                  remove_status(player, card, STATUS_INVISIBLE_FX);
               }
            }
            count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
         }
      }
   }

   if( ! check_status(player, card, STATUS_INVISIBLE_FX) ){
      int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);
      if( result ){
         int amount = BYTE0(result);

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

         card_instance_t *instance = get_card_instance(player, card);

         while( amount ){
               if( can_target(&td) && new_pick_target(&td, "TARGET_PLAYER", 1, 0) ){
                  mill(instance->targets[1].player, 1);
                  instance->number_of_targets = 0;
               }
               amount--;
         }
         kill_card(player, card, KILL_EXILE);
      }
   }

   //Reset counts if the legacy is still invisible
   if( trigger_condition == TRIGGER_GRAVEYARD_FROM_PLAY && event == EVENT_END_TRIGGER ){
      get_card_instance(player, card)->targets[11].player = 0;
   }

   if( event == EVENT_CLEANUP && ! check_status(player, card, STATUS_INVISIBLE_FX) ){
      kill_card(player, card, KILL_EXILE);
   }

   return 0;
}

int card_selhoff_occultist(int player, int card, event_t event){
   /*
   Selhoff Occultist |2|U
   Creature - Human Rogue 2/3
   Whenever ~ or another creature dies, target player puts the top card of his or her library into his or her graveyard.
   */
   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
      int found = 0;
      int c;
      for(c=0; c<active_cards_count[player]; c++){
         if( in_play(player, c) && is_what(player, c, TYPE_EFFECT) ){
            card_instance_t *inst = get_card_instance(player, c);
            if( inst->info_slot == (int)selhoff_occultist_legacy ){
               if( (inst->damage_target_player == player && inst->damage_target_card == card) ||
                  (inst->targets[0].player == player && inst->targets[0].card == card) )
               {
                  found = 1;
                  break;
               }
            }
         }
      }
      if( ! found ){
         int legacy = create_targetted_legacy_effect(player, card, &selhoff_occultist_legacy, player, card);
         card_instance_t *inst = get_card_instance(player, legacy);
         inst->targets[0].player = player;
         inst->targets[0].card = card;
         inst->number_of_targets = 1;
         add_status(player, legacy, STATUS_INVISIBLE_FX);
      }
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
      if( ! affect_me(player, card) ){
         count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
      }
   }

   if( get_card_instance(player, card)->kill_code <= 0 ){
      int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);
      if( result ){
         int amount = BYTE0(result);

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

         card_instance_t *instance = get_card_instance(player, card);

         while( amount ){
               if( can_target(&td) && pick_target(&td, "TARGET_PLAYER") ){
                  mill(instance->targets[0].player, 1);
                  instance->number_of_targets = 0;
               }
               amount--;
         }
      }
   }

   return 0;
}
Two cards that handle everything correctly is Omnath, Locus of Rage and Zulaport Cutthroat and do it more concisely, too. However it would be better if we could find how to fix this in it's current form, too to have two ways working. I didn't test only if it works ( tested and works ) correctly with multiple Zulaport Cutthroat in play - the Kalastria Highborn bug .

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 25 Mar 2021, 12:22
by Aswan jaguar
I have fixed the last bug but before I go on to change all 160+ cards that use this method can someone please tell if a piece of code that is only in few of these cards is useful indeed. Also what it does and how to test if it works?
There is this piece of code
Code: Select all
//Will trigger also if this is not a creature when it goes into the graveyard;
if( ! is_what(p, c, TYPE_CREATURE) ){
The effect card of Falkenrath Noble:
Code: Select all
static int falkenrath_noble_effect(int player, int card, event_t event){

   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
      card_instance_t *instance = get_card_instance(player, card);
      if( instance->damage_target_player > -1 ){
         if( get_id(instance->damage_target_player, instance->damage_target_card) != CARD_ID_FALKENRATH_NOBLE ){
            kill_card(player, card, KILL_EXILE);
         }
      }
   }

   if( get_card_instance(player, card)->damage_target_player > -1 && effect_follows_control_of_attachment(player, card, event) ){
      return 0;
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
      card_instance_t *instance = get_card_instance(player, card);
      if( in_play(affected_card_controller, affected_card) ){
         int kill_code = get_card_instance(affected_card_controller, affected_card)->kill_code;
         if( kill_code ){
            if( instance->damage_target_player > -1 ){
               int p = instance->damage_target_player;
               int c = instance->damage_target_card;
               if( affect_me(p, c) ){
                  //Will trigger also if this is not a creature when it goes into the graveyard;
                  if( ! is_what(p, c, TYPE_CREATURE) ){
                     count_for_gfp_ability(player, card, event, player, TYPE_PERMANENT, NULL);
                     if( instance->targets[11].player < 0 ){
                        instance->targets[11].player = 0;
                     }
                     SET_BYTE1(instance->targets[11].player) = 1;
                  }
                  instance->damage_target_player = instance->damage_target_card = -1;
                  remove_status(player, card, STATUS_INVISIBLE_FX);
               }
            }
            count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
         }
      }
   }

   if( ! check_status(player, card, STATUS_INVISIBLE_FX) ){
      int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);
      if( result ){
         int amount = BYTE0(result);
         life_sucking(player, card, amount);
         kill_card(player, card, KILL_EXILE);
      }
   }

   //Reset counts if the legacy is still invisible
   if( trigger_condition == TRIGGER_GRAVEYARD_FROM_PLAY && event == EVENT_END_TRIGGER ){
      get_card_instance(player, card)->targets[11].player = 0;
   }

   if( ! check_status(player, card, STATUS_INVISIBLE_FX) && event == EVENT_CLEANUP ){
      kill_card(player, card, KILL_EXILE);
   }

   return 0;
}

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 27 Mar 2021, 22:24
by drool66
I think it works like this: it should count itself when it dies, even if it's not a creature, but count_for_gfp_ability() won't track it that way, so increment the count in targets[11].player. However, the code doesn't look right to me - I think it should just be
Code: Select all
                     if( instance->targets[11].player < 0 ){
                        instance->targets[11].player = 0;
                     }
                     instance->targets[11].player++;
Because it's not using mode & (GFPC_TRACK_OWNER | GFPC_TRACK_CONTROLLER), and even if it were, it should be SET_BYTE0() anyway. I can't figure out why that section has another count_for_gfp_ability() either, or why the arguments don't match the card's function.

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 24 Jul 2021, 05:56
by drool66
Aswan jaguar, I just checked this one and it seems to be working properly now - also working correctly with Teysa Karlov. Can you confirm?

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 25 Jul 2021, 15:39
by Aswan jaguar
drool66 wrote:Aswan jaguar, I just checked this one and it seems to be working properly now - also working correctly with Teysa Karlov. Can you confirm?
Which commit you think fixes this? because I haven't integrated your latest changes, yet?

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 25 Jul 2021, 21:43
by drool66
My bad - I fixed it then forgot I had fixed it. The issue was the legacy resetting targets[11].player separately from resolve_gfp_trigger(). All the cards I tested had the same combat bug - Pawn of Ulamog, Butcher of Malakir & Blood Artist. I will update all of them then commit my changes. (only 168 to check :cry: )

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 26 Jul 2021, 05:59
by Aswan jaguar
drool66 wrote:My bad - I fixed it then forgot I had fixed it. The issue was the legacy resetting targets[11].player separately from resolve_gfp_trigger(). All the cards I tested had the same combat bug - Pawn of Ulamog, Butcher of Malakir & Blood Artist. I will update all of them then commit my changes. (only 168 to check :cry: )
I had found that and fixed Selhoff Occultist and (couple of others while testing) locally in 25 Mar 2021 as I have told few posts above. I didn't mention the issue because I meant to fix this myself, just never found time and will to fix all these cards which all have at least one but usually two or more of the bugs mentioned here. As I wanted to fix all of them at once for good and test them ( extremely time consuming to test all these at least for 4-5 different cases ) I always postponed it for a later time. So, I really appreciate you took this task =D> .
So you fixed that only locally? If yes, please post your code for Selhoff Occultist to see how you do it, and test it.

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 26 Jul 2021, 17:47
by drool66
Here is the code. The key is to take resolve_gfp_ability() out from under the invisible check, and remove the part that independently clears targets[11].player
Tested ok so far: while dying in combat, through a Wrath of God & after destroying a bunch of other creatures.
Code: Select all
static int selhoff_occultist_legacy(int player, int card, event_t event){

   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
      card_instance_t *instance = get_card_instance(player, card);
      if( instance->damage_target_player > -1 ){
         if( get_id(instance->damage_target_player, instance->damage_target_card) != CARD_ID_SELHOFF_OCCULTIST ){
            kill_card(player, card, KILL_EXILE);
         }
      }
   }

   if( get_card_instance(player, card)->damage_target_player > -1 && effect_follows_control_of_attachment(player, card, event) ){
      return 0;
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
      card_instance_t* instance = get_card_instance(player, card);
      if( in_play(affected_card_controller, affected_card) ){
         int kill_code = get_card_instance(affected_card_controller, affected_card)->kill_code;
         if( kill_code ){
            if( instance->damage_target_player > -1 ){
               int p = instance->damage_target_player;
               int c = instance->damage_target_card;
               if( affect_me(p, c) ){
                  if( ! is_what(p, c, TYPE_CREATURE) ){
                     if( instance->targets[11].player < 0 )
                        instance->targets[11].player = 0;
                     instance->targets[11].player++;
                  }
                  instance->damage_target_player = instance->damage_target_card = -1;
                  remove_status(player, card, STATUS_INVISIBLE_FX);
               }
            }
         }
      }
      count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
   }

   int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);

   if( result && ! check_status(player, card, STATUS_INVISIBLE_FX)){
      int amount = BYTE0(result);

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

      card_instance_t *instance = get_card_instance(player, card);

      while( amount ){
         if( can_target(&td) && new_pick_target(&td, "TARGET_PLAYER", 1, 0) ){
            mill(instance->targets[1].player, 1);
            instance->number_of_targets = 0;
         }
         amount--;
      }
      kill_card(player, card, KILL_EXILE);
   }

   if( event == EVENT_CLEANUP && ! check_status(player, card, STATUS_INVISIBLE_FX) ){
      kill_card(player, card, KILL_EXILE);
   }

   return 0;
}

int card_selhoff_occultist(int player, int card, event_t event){
   /*
   Selhoff Occultist |2|U
   Creature - Human Rogue 2/3
   Whenever ~ or another creature dies, target player puts the top card of his or her library into his or her graveyard.
   */
   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
      int found = 0;
      int c;
      for(c=0; c<active_cards_count[player]; c++){
         if( in_play(player, c) && is_what(player, c, TYPE_EFFECT) ){
            card_instance_t *inst = get_card_instance(player, c);
            if( inst->info_slot == (int)selhoff_occultist_legacy ){
               if( (inst->damage_target_player == player && inst->damage_target_card == card) ||
                  (inst->targets[0].player == player && inst->targets[0].card == card) )
               {
                  found = 1;
                  break;
               }
            }
         }
      }
      if( ! found ){
         int legacy = create_targetted_legacy_effect(player, card, &selhoff_occultist_legacy, player, card);
         card_instance_t *inst = get_card_instance(player, legacy);
         inst->targets[0].player = player;
         inst->targets[0].card = card;
         inst->number_of_targets = 1;
         add_status(player, legacy, STATUS_INVISIBLE_FX);
      }
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
      if( ! affect_me(player, card) ){
         count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
      }
   }

   if( get_card_instance(player, card)->kill_code <= 0 ){
      int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);
      if( result ){
         int amount = BYTE0(result);

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

         card_instance_t *instance = get_card_instance(player, card);

         while( amount ){
               if( can_target(&td) && pick_target(&td, "TARGET_PLAYER") ){
                  mill(instance->targets[0].player, 1);
                  instance->number_of_targets = 0;
               }
               amount--;
         }
      }
   }

   return 0;
}

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 27 Jul 2021, 16:58
by Aswan jaguar
Yes, it works fine in all cases. Do you know how to test if dies and is not a creature?
For reference what worked for me with some luck and probably worse than your code.
Code: Select all
static int selhoff_occultist_legacy(int player, int card, event_t event){

   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
     card_instance_t *instance = get_card_instance(player, card);
     if( instance->damage_target_player > -1 ){
       if( get_id(instance->damage_target_player, instance->damage_target_card) != CARD_ID_SELHOFF_OCCULTIST ){
         kill_card(player, card, KILL_EXILE);
       }
     }
   }

   if( get_card_instance(player, card)->damage_target_player > -1 && effect_follows_control_of_attachment(player, card, event) ){
     return 0;
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
     card_instance_t *instance = get_card_instance(player, card);
     if( in_play(affected_card_controller, affected_card) ){
       int kill_code = get_card_instance(affected_card_controller, affected_card)->kill_code;
       if( kill_code ){
         if( instance->damage_target_player > -1 ){
            int p = instance->damage_target_player;
            int c = instance->damage_target_card;
            if( affect_me(p, c) ){
              if( is_this_dying(p, c) ){
                SET_BYTE1(instance->targets[11].player)++;
              if( instance->targets[11].player < 0 ){
                instance->targets[11].player = 0;
              }
              SET_BYTE1(instance->targets[11].player) = 1;
              }
              instance->damage_target_player = instance->damage_target_card = -1;
              remove_status(player, card, STATUS_INVISIBLE_FX);
            }
         }
         count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
       }
     }
   }

   if( ! check_status(player, card, STATUS_INVISIBLE_FX) ){
     int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);
     if( result ){
       int amount = BYTE0(result);

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

       card_instance_t *instance = get_card_instance(player, card);

       while( amount ){
            if( can_target(&td) && new_pick_target(&td, "TARGET_PLAYER", 1, 0) ){
              mill(instance->targets[1].player, 1);
              instance->number_of_targets = 0;
            }
            amount--;
       }
       kill_card(player, card, KILL_EXILE);
     }
   }
   // responsible for working only in player's turn.
   //Reset counts if the legacy is still invisible
  /* if( trigger_condition == TRIGGER_GRAVEYARD_FROM_PLAY && event == EVENT_END_TRIGGER ){
     get_card_instance(player, card)->targets[11].player = 0;
   }*/

   if( event == EVENT_CLEANUP && ! check_status(player, card, STATUS_INVISIBLE_FX) ){
     kill_card(player, card, KILL_EXILE);
   }

   return 0;
}

int card_selhoff_occultist(int player, int card, event_t event){
   
   Selhoff Occultist |2|U
   Creature - Human Rogue 2/3
   Whenever ~ or another creature dies, target player puts the top card of his or her library into his or her graveyard.
   
   if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
     int found = 0;
     int c;
     for(c=0; c<active_cards_count[player]; c++){
       if( in_play(player, c) && is_what(player, c, TYPE_EFFECT) ){
         card_instance_t *inst = get_card_instance(player, c);
         if( inst->info_slot == (int)selhoff_occultist_legacy ){
            if( (inst->damage_target_player == player && inst->damage_target_card == card) ||
              (inst->targets[0].player == player && inst->targets[0].card == card) )
            {
              found = 1;
              break;
            }
         }
       }
     }
     if( ! found ){
       int legacy = create_targetted_legacy_effect(player, card, &selhoff_occultist_legacy, player, card);
       card_instance_t *inst = get_card_instance(player, legacy);
       inst->targets[0].player = player;
       inst->targets[0].card = card;
       inst->number_of_targets = 1;
       add_status(player, legacy, STATUS_INVISIBLE_FX);
     }
   }

   if( event == EVENT_GRAVEYARD_FROM_PLAY ){
     if( ! affect_me(player, card) ){
       count_for_gfp_ability(player, card, event, ANYBODY, TYPE_CREATURE, NULL);
     }
   }

   if( get_card_instance(player, card)->kill_code <= 0 ){
     int result = resolve_gfp_ability(player, card, event, RESOLVE_TRIGGER_MANDATORY);
     if( result ){
       int amount = BYTE0(result);

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

       card_instance_t *instance = get_card_instance(player, card);

       while( amount ){
            if( can_target(&td) && pick_target(&td, "TARGET_PLAYER") ){
              mill(instance->targets[0].player, 1);
              instance->number_of_targets = 0;
            }
            amount--;
       }
     }
   }

   return 0;
}

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 27 Jul 2021, 22:55
by drool66
The only way I could find to remove the creature type without removing its abilities was with One with the Stars; I coded that up and wouldn't you know, Selhof Occultist does trigger on its own death when it's not a creature

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 28 Jul 2021, 06:36
by Korath
You're factoring all that out and not just cut-and-pasting it into the two dozen other cards templated like that, right?

Re: [confirmed]Selhoff Occultist dying simultaneously proble

PostPosted: 29 Jul 2021, 00:03
by drool66
My plan is to individually evaluate those cards, as well as all of the permanents that could possibly trigger because of, or otherwise at the time of, their own death, which also includes many of these and at least a few of these-eg. Disciple of the Vault & Fangren Maurader - these different cases will all need slightly different implementations of this code.