Page 1 of 1

Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 03 Jul 2018, 19:20
by doenerchef
Does anyone know, if either Oracle of Mul Daya or Courser of Kruphix are in the game (i.e. in any version of the game that you are aware of)?

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 06 Jul 2018, 03:30
by Korath
They're not.

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 09 Jul 2018, 08:33
by doenerchef
Thank you! Are those cards where the game engine would have made it very hard to bring them into play or is it simply a case of "not yet"?

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 09 Jul 2018, 20:56
by Korath
It's mostly a matter of the interface. There's entirely too many steps to get this engine to do it right - to acknowledge clicks on the library at all, to interpret those clicks as an attempt to cast (whether at sorcery speed, when the stack is empty but not during the main phase, in response to something on the stack, and for the AI to do it in each of those three situations - it's done in a different place, in a slightly different way, for each of those six cases), and to add or remove the yellow border to the card when it becomes castable or no-longer-castable - and I didn't do a good enough job of documenting them all when I added it for cards in the graveyard and exile. Manalink sidesteps the problem by making it an activated ability of the Oracle or Courser or Future Sight or whatever, which sort of works most of the time but gets the corner cases wrong.

(For that matter, the graveyard/exile implementation is still badly incomplete; it only allows activation or playing cards in the graveyard or exile if it's an ability the card itself has, for the single special case of Crucible of Worlds / Ramunap Excavator, and for casting a single card during the resolution of another spell or ability. So nothing like Yawgmoth's Will or Haakon, Stromgald Scourge or Snapcaster Mage or Mind's Desire.)

In contrast, getting spells to actually be playable from the library isn't all that hard (even considering that you might cancel during targeting after paying the mana cost with something like Millikin, and there's no provision in this engine to "roll the game state back" as the rules require, nor any easy way to add such a provision). I actually did that recently, for Djinn of Wishes, Aetherworks Marvel, and seven other cards that do the casting during their resolutions, where the interface isn't at issue.

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 16 Jul 2018, 18:31
by doenerchef
Thank you for that thorough answer! I figured that was the reason and while reading your explanation the solution that Manalink uses also came to my mind, to make the "play the top card"-ability accessible via Oracle/Courser. But since those are significant cards, I figure if it was possible, you would already have added those.

Since we're talking about significant cards that aren't in the game, I assume it was the same reason (i.e. incompatibility with the interface) that Force of Will and Fireblast were never added?

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 16 Jul 2018, 18:58
by Korath
Nope. Cards in hand or on the stack are treated only a little differently from cards on the battlefield, so alternative casting costs are fairly easy. Activating abilities of cards in hands, such as cycling, is only a little more difficult than that, without even resorting to Manalink's workaround of activating a fake Rules Engine card on the battlefield instead. The only real problem here was working out a proper internal interface so activations and alternative/additional costs could be expressed concisely instead of in pages of boilerplate code. Force of Will (only) made it into the last public release, IIRC, by resorting to the boilerplate.

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 16 Jul 2018, 19:36
by doenerchef
Aw, cool! I have to look into that. Boilerplate is those big popups, like the one you get for Nettle Sentinel where you can click "Trigger" or "Decline"?

Re: Oracle of Mul Daya or Courser of Kruphix in the game?

PostPosted: 16 Jul 2018, 20:39
by Korath
Completely different. (Wikipedia article.) The idea is to have Force of Will's implementation itself only have to say "I can be cast for exiling a blue card and paying 1 life" and "counter target spell", instead of having it deal with the low-level mechanics of prompting for which card to exile, exiling it, making sure the player can pay life, suppressing the normal mana cost, disallowing the alternative payment if it's already being paid by a different alternative cost like Fist of Suns, etc.

As a concrete example, Manalink's implementation of Fireblast looks like:
Code: Select all
{
    if( event == EVENT_MODIFY_COST ){
        test_definition_t test;
        default_test_definition(&test, TYPE_LAND);
        test.subtype = SUBTYPE_MOUNTAIN;
        test.qty = 2;
        if( new_can_sacrifice_as_cost(player, card, &test) ){
            get_card_instance(player, card)->info_slot = 1;
            null_casting_cost(player, card);
        }
        else{
            get_card_instance(player, card)->info_slot = 0;
        }
    }

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

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

    card_instance_t *instance = get_card_instance(player, card);

    if( event == EVENT_CAN_CAST ){
        return generic_spell(player, card, event, GS_CAN_TARGET, &td, NULL, 1, NULL);
    }

    if( event == EVENT_CAST_SPELL && affect_me(player, card) ){
        if( ! played_for_free(player, card) && ! is_token(player, card) && instance->info_slot == 1 ){
            int choice = 1;
            if( has_mana_to_cast_iid(player, event, get_card_instance(player, card)->internal_card_id) ){
                choice = DIALOG(player, card, event, DLG_RANDOM, DLG_NO_STORAGE,
                                "Sacrifice two mountains", 1, count_subtype(player, TYPE_LAND, -1) * 2,
                                "Cast normally", 1, 5);
                if( ! choice ){
                    spell_fizzled = 1;
                    return 0;
                }
            }

            if( choice == 1 ){
                test_definition_t test;
                new_default_test_definition(&test, TYPE_LAND, "Select a Mountain to sacrifice.");
                test.subtype = SUBTYPE_MOUNTAIN;    // not hacked; it's a cost, so there's no opportunity to cast Magical Hack

                int sacced[2] = {-1};
                int i;
                for(i=0; i<2; i++){
                    sacced[i] = new_sacrifice(player, card, player, SAC_JUST_MARK|SAC_AS_COST|SAC_RETURN_CHOICE, &test);
                    if( sacced[i] == -1 ){
                        spell_fizzled = 1;
                        break;
                    }
                }
                for(i=0; i<2; i++){
                    if( sacced[i] != -1 ){
                        state_untargettable(BYTE2(sacced[i]), BYTE3(sacced[i]), 0);
                        if( spell_fizzled != 1 ){
                            kill_card(BYTE2(sacced[i]), BYTE3(sacced[i]), KILL_SACRIFICE);
                        }
                    }
                }
                if( spell_fizzled == 1 ){
                    return 0;
                }
                td.allow_cancel = 0;
            }

            if( choice == 1 ){
                charge_mana_from_id(player, -1, event, get_id(player, card));
            }
        }
        return generic_spell(player, card, event, GS_CAN_TARGET, &td, "TARGET_CREATURE_OR_PLAYER", 1, NULL);
    }

    if( event == EVENT_RESOLVE_SPELL ){
        if( valid_target(&td) ){
            damage_target0(player, card, 4);
        }
        kill_card(player, card, KILL_DESTROY);
    }

    return 0;
}
Shandalar's is
Code: Select all
{
  if (ALTERNATIVE_COST_EVENT())
    return alternative_cost_sac(player, card, event, SELECT_LANDTYPE(player, SUBTYPE_MOUNTAIN), 2);

  return SPELL_TARGET_ANY(1-player,
                          deal_damage({player, card}, 4, inst->targets[0]));
}
The former's not so bad if there's only one card that lets you cast it by sacrificing stuff instead, and no chance of another ever being printed. But that's not the case, and so much of it gets copied and pasted over and over and over again into every card that needs it, and if there's something wrong, it only gets fixed in one of the cards that uses it.