Card Development - talk about cards code here
Discuss Upcoming Releases, Coding New Cards, Etc.
PLEASE DO NOT REPORT BUGS HERE!
PLEASE DO NOT REPORT BUGS HERE!
Moderators: BAgate, drool66, Aswan jaguar, gmzombie, stassy, CCGHQ Admins
DONE - Elvish Healer target opponent issue
by Aswan jaguar » 12 Nov 2020, 15:35
I have made Elvish Healer but not using the usual Manalink method to target the damage_card but went for the prevention shield way. The issue happens only if you target opponent. The prevention shield effect doesn't go to opponent's field like it does with other cards like Hold at Bay (shows as not owned by that player) doesn't have the point of damage on it, doesn't prevent damage and produces a rukh token at eot.
See this post for reason for and against prevention shield:
viewtopic.php?f=86&t=16985&p=194613&hilit=prevention+shield#p194626
I don't know if it is possible but if we could make prevention shield to work both ways like the regeneration shield does it would be the best solution. Although I fear that Korath didn't implement the function to use both ways because it is not possible. For me if it comes to choose one way although I like the manalink way, it produces more bugs than the shield one does, so I go for shield.
See this post for reason for and against prevention shield:
viewtopic.php?f=86&t=16985&p=194613&hilit=prevention+shield#p194626
I don't know if it is possible but if we could make prevention shield to work both ways like the regeneration shield does it would be the best solution. Although I fear that Korath didn't implement the function to use both ways because it is not possible. For me if it comes to choose one way although I like the manalink way, it produces more bugs than the shield one does, so I go for shield.
- Code: Select all
int card_elvish_healer(int player, int card, event_t event){
/* CARD_ID_ELVISH_HEALER 1912
Elvish Healer |2|W
Creature - Elf Cleric 1/2
|T: Prevent the next 1 damage that would be dealt to any target this turn. If it's a |Sgreen creature, prevent the next 2 damage instead.*/
if( ! IS_GAA_EVENT(event) ){
return 0;
}
target_definition_t td;
default_target_definition(player, card, &td, TYPE_CREATURE|TARGET_TYPE_PLANESWALKER);
td.preferred_controller = player;
td.zone = TARGET_ZONE_CREATURE_OR_PLAYER;
if (event == EVENT_CAN_ACTIVATE){
int gaa_pre = generic_activated_ability(player, card, event, GAA_CAN_TARGET | GAA_UNTAPPED, MANACOST0, 0, &td, NULL);
return ! gaa_pre ? 0 : (land_can_be_played & LCBP_DAMAGE_PREVENTION) ? 99 : 1; // so that it can work as a prevention shield and at manalink time.
}
if( event == EVENT_ACTIVATE ){
card_instance_t* instance = get_card_instance(player, card);
instance->number_of_targets = 0;
generic_activated_ability(player, card, event, GAA_CAN_TARGET | GAA_LITERAL_PROMPT | GAA_UNTAPPED, MANACOST0, 0,
&td, "Select any target to prevent next 1 damage or 2 for a green creature.");
}
if( event == EVENT_RESOLVE_ACTIVATION ){
if( valid_target(&td) ){
card_instance_t* instance = get_card_instance(player, card);
int targ_p = instance->targets[0].player;
int targ_c = instance->targets[0].card;
if( instance->targets[0].card >= 0 && (get_color(targ_p, targ_c) & get_sleighted_color_test(targ_p, targ_c, COLOR_TEST_GREEN)) ){
prevent_the_next_n_damage(player, card, targ_p, targ_c, 2, 0, 0, 0);
}
else{
prevent_the_next_n_damage(player, card, targ_p, targ_c, 1, 0, 0, 0);
}
}
}
return 0;
}
Last edited by Aswan jaguar on 21 Nov 2020, 16:34, edited 1 time in total.
Reason: tag
Reason: tag
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 12 Nov 2020, 23:27
You just need to get the parent for effect resolution:
For finishing touches you could also use get_sleighted_color_text() in EVENT_ACTIVATE
One thing I've been meaning to bring up is valid_target() vs. validate_target() in EVENT_RESOLVE_ACTIVATION/_SPELL. I think valid_target() is just "are there any valid targets?", whereas validate_target() is "is the thing I've targeted a valid target?" So if the target becomes invalid between activation/cast & resolution but there is another valid target available, valid_target() will return true when you want it to return false, whereas validate_target() will not. Is that right? I've been changing these in the cards I've worked on, but not on every one I see since it's just too many.
- Code: Select all
if( event == EVENT_RESOLVE_ACTIVATION ){
if( validate_target(player, card, &td, 0) ){
card_instance_t* instance = get_card_instance(player, card);
card_instance_t* parent = get_card_instance(instance->parent_controller, instance->parent_card);
int targ_p = parent->targets[0].player;
int targ_c = parent->targets[0].card;
if( targ_c >= 0 && (get_color(targ_p, targ_c) & get_sleighted_color_test(instance->parent_controller, instance->parent_card, COLOR_TEST_GREEN)) ){
prevent_the_next_n_damage(instance->parent_controller, instance->parent_card, targ_p, targ_c, 2, 0, 0, 0);
}
else{
prevent_the_next_n_damage(instance->parent_controller, instance->parent_card, targ_p, targ_c, 1, 0, 0, 0);
}
}
}
For finishing touches you could also use get_sleighted_color_text() in EVENT_ACTIVATE
- Code: Select all
get_sleighted_color_text(player, card, "Select any target to prevent next 1 damage or 2 for a %s creature.", COLOR_GREEN)
One thing I've been meaning to bring up is valid_target() vs. validate_target() in EVENT_RESOLVE_ACTIVATION/_SPELL. I think valid_target() is just "are there any valid targets?", whereas validate_target() is "is the thing I've targeted a valid target?" So if the target becomes invalid between activation/cast & resolution but there is another valid target available, valid_target() will return true when you want it to return false, whereas validate_target() will not. Is that right? I've been changing these in the cards I've worked on, but not on every one I see since it's just too many.
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
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
by Korath » 13 Nov 2020, 04:41
Read the source, Luke:drool66 wrote:I think valid_target() is just "are there any valid targets?", whereas validate_target() is "is the thing I've targeted a valid target?"
- Code: Select all
int valid_target(target_definition_t *td ){
return validate_target(td->player, td->card, td, 0);
}
-
Korath - DEVELOPER
- Posts: 3707
- Joined: 02 Jun 2013, 05:57
- Has thanked: 496 times
- Been thanked: 1106 times
[DONE]Hipparion prompt & vs must block
by Aswan jaguar » 21 Nov 2020, 17:29
With Hipparion the issue I have is that it prompts for mana once before choosing blockers ( EVENT_BLOCK_LEGALITY right? )and then re-prompts after I choose which attacker it should block. If I cancel the first prompt and pay the second it still works.
- Code: Select all
int card_hipparion(int player, int card, event_t event){//in progress
/*Hipparion |1|W 0x000000
* Creature - Horse 1/3
* ~ can't block creatures with power 3 or greater unless you pay |1. */
if (event == EVENT_BLOCK_LEGALITY && current_phase == PHASE_DECLARE_BLOCKERS && affect_me(player, card) && !is_humiliated(player, card)
&& get_power(attacking_card_controller, attacking_card) >= 3)
{
if(!has_mana(player, COLOR_COLORLESS, 1)){
event_result = 1;
}
else if( charge_mana_while_resolving(player, card, 0, player, COLOR_COLORLESS, 1) ){
if(spell_fizzled !=1){
event_result = 0;
return 0;
}
}
}
return 0;
}
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 21 Nov 2020, 19:04
Looking at engine.c > human_assign_blockers(), the next thing I would try would be to add "instance->state & STATE_BLOCKING" to your event conditions. EVENT_BLOCK_LEGALITY is first checked at 0x434E70 ("any_can_block") - you don't want Hipparion to trigger here. I don't see it checked in 0x4350E0 (or 0x434F30, which it calls), so maybe it's done during TRIGGER_BLOCKER_CHOSEN (which would probably call 0x434E70 again). If that's true, this fix should work, since STATE_BLOCKING is added between the two checks in human_assign_blockers().
EDIT: Found it. Event is dispatched in 0x434E70 and in is_legal_block_impl(), the latter of which is called from 0x434F30 ie. is_legal_block(). This is unfortunate because nothing meaningful is set between the two events. I guess you could use an internal variable like:
EDIT: Found it. Event is dispatched in 0x434E70 and in is_legal_block_impl(), the latter of which is called from 0x434F30 ie. is_legal_block(). This is unfortunate because nothing meaningful is set between the two events. I guess you could use an internal variable like:
- Code: Select all
int card_hipparion(int player, int card, event_t event){//in progress
/*Hipparion |1|W 0x000000
* Creature - Horse 1/3
* ~ can't block creatures with power 3 or greater unless you pay |1. */
if (event == EVENT_BLOCK_LEGALITY && current_phase == PHASE_DECLARE_BLOCKERS && affect_me(player, card) && !is_humiliated(player, card) && get_power(attacking_card_controller, attacking_card) >= 3)
{
if( get_card_instance(player, card)->info_slot == 66){
if(!has_mana(player, COLOR_COLORLESS, 1)){
event_result = 1;
}
else if( !charge_mana_while_resolving(player, card, 0, player, COLOR_COLORLESS, 1) ){
if(spell_fizzled !=1){
event_result = 0;
return 0;
}
}
get_card_instance(player, card)->info_slot = 0;
}
else{
get_card_instance(player, card)->info_slot = 66;
}
return 0;
}
return 0;
}
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
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
by Aswan jaguar » 22 Nov 2020, 12:37
I have also fixed that you could cancel to pay mana and then be able to make the illegal block with Hipparion.
There is also an annoying little "bug" that you are asked to pay mana even if you don't have available mana (which is not the case with the previous bugged to pay twice version). If you have an idea how to fix that without messing everything else (which is what I did trying to fix this) I will wait for that tip otherwise the card works rules wise and I will commit it, thanks.
There is also an annoying little "bug" that you are asked to pay mana even if you don't have available mana (which is not the case with the previous bugged to pay twice version). If you have an idea how to fix that without messing everything else (which is what I did trying to fix this) I will wait for that tip otherwise the card works rules wise and I will commit it, thanks.
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 23 Nov 2020, 03:50
The version I posted above doesn't reliably clear info_slot - I just banged it out before bed. But even if it is managed properly, the dispatch from any_can_block() doesn't happen reliably due to something involving dword_4EF520[].
I have come up with a "clean-ish" solution that works 100% perfectly as far as I can tell. It involves adding a variable to check where you are in the declare attackers cycle. Looks like this:
engine.c
This isn't an incredibly hackneyed fix, but it does feel weird to add a variable to a function called most turns of most games to serve exactly one card. On the other hand, Ice Age cards are weird and we did just add a new event just for Balduvian Shaman, so eh...
As a side note, TRIGGER_PAY_TO_BLOCK won't work because it's dispatched before blockers are chosen, and this tax depends on the chosen blocker.
I have come up with a "clean-ish" solution that works 100% perfectly as far as I can tell. It involves adding a variable to check where you are in the declare attackers cycle. Looks like this:
engine.c
- Code: Select all
int hack_second_dispatch_event_block_legality = 0;
void human_assign_blockers(int player)
{
[skip down to...]
while (EXE_FN(int, 0x434E70, int)(1-player)) // any_can_block() <--first check of EVENT_BLOCK_LEGALITY; variable still 0
[...]
if (!forbid_attack
&& select_target(player, -1000, &td_block_which_attacker, text_lines[1], &blocked))
{
//Begin additions
hack_second_dispatch_event_block_legality = 1; <--set to 1 before second check happens in try_block() - this is where we want to check the event
//End additions
// 0x4350e0 clears blocking, checks legality, and then, if it was illegal, restores the previous values
if (EXE_FN(int, 0x4350E0, int, int, int, int)(blocker.player, blocker.card, blocked.player, blocked.card)) // try_block()
{
int band = get_card_instance(blocked.player, blocked.card)->blocking;
if (band == 255)
band = blocked.card;
card_instance_t* instance = get_card_instance(blocker.player, blocker.card);
instance->blocking = band;
instance->state |= STATE_BLOCKING;
// Begin removals
// if (band_before_being_set_to_blocked.card != 255) // Suppresse the block sound when blocking a band
// End removals
play_sound_effect(WAV_BLOCK2);
EXE_FN(void, 0x472260, void)(); // TENTATIVE_reassess_all_cards()
if (event_flags & EA_SELECT_BLOCK)
dispatch_trigger2(current_turn, TRIGGER_BLOCKER_CHOSEN, EXE_STR(0x790074)/*PROMPT_BLOCKERSELECTION[0]*/, 0, blocker.player, blocker.card);
}
else // if (ai_is_speculating != 1) // Redundant, already checked above
{
load_text(0, "PROMPT_CHOOSEBLOCKERS");
set_centerwindow_txt(text_lines[2]);
EXE_STDCALL_FN(void, 0x4D5D32, int)(2000); // Sleep(2000)
set_centerwindow_txt("");
}
//Begin additions
hack_second_dispatch_event_block_legality = 0; <-- clear before end of loop, ie. before we go back to the dispatch in any_can_block()
//End additions
}
{
This isn't an incredibly hackneyed fix, but it does feel weird to add a variable to a function called most turns of most games to serve exactly one card. On the other hand, Ice Age cards are weird and we did just add a new event just for Balduvian Shaman, so eh...
As a side note, TRIGGER_PAY_TO_BLOCK won't work because it's dispatched before blockers are chosen, and this tax depends on the chosen blocker.
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
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
by Aswan jaguar » 23 Nov 2020, 15:39
Yes, it works better with this variable. I think also Awesome Presence needs the same or similar approach but I am not touching it, as it is more complicated than Hipparion and I don't want to .
Current card's code (I don't include the needed changes in engine.c that you have already posted) & (still prompts even if you don't have available mana but at least now works fine ).
Current card's code (I don't include the needed changes in engine.c that you have already posted) & (still prompts even if you don't have available mana but at least now works fine ).
- Code: Select all
int hack_second_dispatch_event_block_legality;
int card_hipparion(int player, int card, event_t event){//in progress
/*Hipparion |1|W
* Creature - Horse 1/3
* ~ can't block creatures with power 3 or greater unless you pay |1. */
if (event == EVENT_BLOCK_LEGALITY && current_phase == PHASE_DECLARE_BLOCKERS && affect_me(player, card) && !is_humiliated(player, card)
&& get_power(attacking_card_controller, attacking_card) >= 3 && hack_second_dispatch_event_block_legality)
{
if(!has_mana(player, COLOR_COLORLESS, 1)){
event_result = 1;
}
else if( !charge_mana_while_resolving(player, card, 0, player, COLOR_COLORLESS, 1) ){
if(spell_fizzled !=1){
event_result = 0;
return 0;
}
event_result = 1;
}
}
return 0;
}
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 24 Nov 2020, 18:41
Oof, that's real Gordian Knot. So SP_KEYWORD_MUST_BLOCK forwards to block_if_able() on EVENT_DECLARE_BLOCKERS, then to select_blocker(), whose target definition includes can_block_target(), calling can_block_me(), which returns is_legal_block() / is_legal_block_impl(), which returns the event_result (well, actually its binary opposite) of EVENT_BLOCK_LEGALITY. Issue is I can't tell exactly when EVENT_DECLARE_BLOCKERS is dispatched. FWIW, here is my current state of hippparion & human_assign_blockers() - most of it is a mess & will probably end up being unnecessary, but I'm trying to isolate this call to EVENT_BLOCK_LEGALITY:
Hipparion:
Hipparion:
- Code: Select all
int hack_first_call_to_event_block_legality;
int hack_second_call_to_event_block_legality;
int hack_cancel_blockers;
int card_hipparion(int player, int card, event_t event){//in progress
/*Hipparion |1|W 0x000000
* Creature - Horse 1/3
* ~ can't block creatures with power 3 or greater unless you pay |1. */
if ((event == EVENT_BLOCK_LEGALITY || event == EVENT_DECLARE_BLOCKERS) && current_phase == PHASE_DECLARE_BLOCKERS && affect_me(player, card) && !is_humiliated(player, card) && get_power(attacking_card_controller, attacking_card) >= 3 ){
ASSERT(!(get_card_instance(player, card)->info_slot == 0xFF));
if( hack_cancel_blockers ){
get_card_instance(player, card)->info_slot |= 0xF;
get_card_instance(player, card)->targets[16].card &= ~SP_KEYWORD_MUST_BLOCK;
}
if( hack_second_call_to_event_block_legality && !(get_card_instance(player, card)->info_slot & 0xFF)){
if(!has_mana(player, COLOR_COLORLESS, 1)){
event_result |= 1;
}
else if( !charge_mana_while_resolving(player, card, 0, player, COLOR_COLORLESS, 1) ){
if(spell_fizzled !=1){
get_card_instance(player, card)->info_slot |= 0xF0;//paid
get_card_instance(player, card)->info_slot &= ~0xF;//canceled
event_result = 0;
return 0;
}
get_card_instance(player, card)->info_slot |= 0xF;//canceled
event_result |= 1;
}
}
else if(!hack_first_call_to_event_block_legality && !(get_card_instance(player, card)->info_slot & 0xFF) ){//outside of normal blocking loop & hasn't been prompted
if(!has_mana(player, COLOR_COLORLESS, 1)){
event_result |= 1;
}
else if( !charge_mana_while_resolving(player, card, 0, player, COLOR_COLORLESS, 1) ){
if(spell_fizzled !=1){
get_card_instance(player, card)->info_slot |= 0xF0;//paid
get_card_instance(player, card)->info_slot &= ~0xF;//canceled
event_result = 0;
return 0;
}
get_card_instance(player, card)->info_slot |= 0xF;//canceled
event_result |= 1;
}
}
else if( hack_first_call_to_event_block_legality && !(get_card_instance(player, card)->info_slot & 0xF0) && !has_mana(player, COLOR_COLORLESS, 1))
event_result |= 1;
else if(!hack_first_call_to_event_block_legality && get_card_instance(player, card)->info_slot & 0xF0 && !(get_card_instance(player, card)->info_slot & 0xF))
event_result = 0;
else if(!hack_first_call_to_event_block_legality && get_card_instance(player, card)->info_slot & 0xF && !(get_card_instance(player, card)->info_slot & 0xF0))
event_result |= 1;
}
if( event == EVENT_PHASE_CHANGED && (current_phase >= PHASE_MAIN2 || current_phase <= PHASE_MAIN1))
get_card_instance(player, card)->info_slot = 0;
return 0;
}
- Code: Select all
int hack_first_call_to_event_block_legality = 0;
int hack_second_call_to_event_block_legality = 0;
int hack_cancel_blockers = 0;
void human_assign_blockers(int player)
{
// 0x434960
if (ai_is_speculating == 1)
return;
// Begin additions
int who_chooses;
if (event_flags & EF_ATTACKER_CHOOSES_BLOCKERS)
{
who_chooses = player;
if (current_turn == AI) // Nothing blocks
return;
}
else
who_chooses = 1-player;
// End additions
EXE_FN(void, 0x472260, void)(); // TENTATIVE_reassess_all_cards()
// Begin removals
#if 0
// This seems to be the remains of primitive AI handling; the values it computes aren't used.
int indices[16], powers[16], highest_power = 0, pos = 0, c;
for (c = 0; active_cards_count[player] > c; ++c)
{
card_instance_t* instance = get_card_instance(player, c);
if (instance->internal_card_id != -1 && (instance->state & STATE_ATTACKING))
{
int pow = get_abilities(player, c, EVENT_POWER, -1);
indices[pos] = c;
powers[pos] = pow;
++pos;
if (highest_power < pow)
highest_power = pow;
}
}
#endif
// End removals
target_definition_t td_choose_blocker;
base_target_definition(1-player, 0, &td_choose_blocker, TARGET_TYPE_NONCREATURE_CAN_BLOCK | TYPE_CREATURE);
td_choose_blocker.who_chooses = who_chooses;
td_choose_blocker.allowed_controller = 1-player;
td_choose_blocker.preferred_controller = 1-player;
td_choose_blocker.zone = TARGET_ZONE_0x2000 | TARGET_ZONE_IN_PLAY;
td_choose_blocker.special = TARGET_SPECIAL_ALLOW_MULTIBLOCKER;
td_choose_blocker.illegal_state = TARGET_STATE_BLOCKING | TARGET_STATE_TAPPED;
td_choose_blocker.allow_cancel = 2; // I suspect this is what makes the "Done" button.
target_definition_t td_block_which_attacker;
base_target_definition(player, 0, &td_block_which_attacker, TYPE_CREATURE);
td_block_which_attacker.who_chooses = who_chooses;
td_block_which_attacker.allowed_controller = player;
td_block_which_attacker.preferred_controller = player;
td_block_which_attacker.required_state = TARGET_STATE_ATTACKING;
target_t blocker, blocked;
hack_first_call_to_event_block_legality = 1;
while (EXE_FN(int, 0x434E70, int)(1-player)) // any_can_block()
{
hack_first_call_to_event_block_legality = 0;
load_text(0, "PROMPT_CHOOSEBLOCKERS");
if (!select_target(1-player, -1000, &td_choose_blocker, text_lines[0], &blocker))
return;
forbid_attack = 0;
if (event_flags & EA_PAID_BLOCK)
{
push_affected_card_stack();
trigger_cause_controller = blocker.player;
trigger_cause = blocker.card;
dispatch_trigger(1-player, TRIGGER_PAY_TO_BLOCK, EXE_STR(0x790248)/*PROMPT_TURNSEQUENCE[0]*/, 1);
pop_affected_card_stack();
}
if (!forbid_attack
&& select_target(player, -1000, &td_block_which_attacker, text_lines[1], &blocked))
{
hack_second_call_to_event_block_legality = 1;
// 0x4350e0 clears blocking, checks legality, and then, if it was illegal, restores the previous values
if (EXE_FN(int, 0x4350E0, int, int, int, int)(blocker.player, blocker.card, blocked.player, blocked.card)) // try_block()
{
hack_second_call_to_event_block_legality = 0;
int band = get_card_instance(blocked.player, blocked.card)->blocking;
if (band == 255)
band = blocked.card;
card_instance_t* instance = get_card_instance(blocker.player, blocker.card);
instance->blocking = band;
instance->state |= STATE_BLOCKING;
// Begin removals
// if (band_before_being_set_to_blocked.card != 255) // Suppresse the block sound when blocking a band
// End removals
play_sound_effect(WAV_BLOCK2);
EXE_FN(void, 0x472260, void)(); // TENTATIVE_reassess_all_cards()
if (event_flags & EA_SELECT_BLOCK)
dispatch_trigger2(current_turn, TRIGGER_BLOCKER_CHOSEN, EXE_STR(0x790074)/*PROMPT_BLOCKERSELECTION[0]*/, 0, blocker.player, blocker.card);
}
else // if (ai_is_speculating != 1) // Redundant, already checked above
{
hack_cancel_blockers = 1;
load_text(0, "PROMPT_CHOOSEBLOCKERS");
set_centerwindow_txt(text_lines[2]);
EXE_STDCALL_FN(void, 0x4D5D32, int)(2000); // Sleep(2000)
set_centerwindow_txt("");
}
}
hack_first_call_to_event_block_legality = 1;
}
hack_first_call_to_event_block_legality = 0;
hack_cancel_blockers = 0;
}
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
drool66 - Programmer
- Posts: 1163
- Joined: 25 Nov 2010, 22:38
- Has thanked: 186 times
- Been thanked: 267 times
Infinite Hourglass bad AI reactivation behaviour
by Aswan jaguar » 27 Nov 2020, 17:18
Infinite Hourglass is working but has a bad AI issue which is described in my comments above card's code. Is there any other method to stop AI from re-activating cards while on stack? Especially while we force AI to activate the card because it is the upkeep phase like in this case?
- Code: Select all
static int infinite_hourglass_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->targets[0].player > -1 ){
if( get_id(instance->targets[0].player, instance->targets[0].card) != CARD_ID_INFINITE_HOURGLASS ){
kill_card(player, card, KILL_EXILE);
}
if( ! in_play(instance->targets[0].player, instance->targets[0].card) ){
kill_card(player, card, KILL_EXILE);
}
}
}
if( get_card_instance(player, card)->targets[0].player != -1 ){
card_instance_t *instance = get_card_instance(player, card);
if (!IS_GAA_EVENT(event)){
return 0;
}
int p = instance->targets[0].player;
int c = instance->targets[0].card;
int rval = granted_generic_activated_ability(p, c, player, card, event, GAA_ONLY_ON_UPKEEP, MANACOST_X(3), 0, NULL, NULL);
if( event == EVENT_CAN_ACTIVATE ){
if (current_phase != PHASE_UPKEEP
|| count_counters(p, c, COUNTER_TIME) <= 0)
//|| !has_mana_for_activated_ability(p, c, MANACOST_X(3)))
// || get_card_instance(p, c)->info_slot & 1)
return 0;
if( rval && IS_AI(player) && count_counters(p, c, COUNTER_TIME) > 0 ){
if( count_subtype(p, TYPE_CREATURE, -1)+1 < count_subtype(1-player, TYPE_CREATURE, -1) ){
EXE_DWORD(0x736808) |= 3;
}
}
}
/*if (event == EVENT_ACTIVATE)
get_card_instance(p, c)->info_slot |= 1; */
if (event == EVENT_RESOLVE_ACTIVATION){
remove_counter(p, c, COUNTER_TIME);
}
/*if( event == EVENT_STATIC_EFFECTS ){
if (stack_size == 0){ //prohibit activation on stack.
get_card_instance(p, c)->info_slot = 0;
}
}*/
return rval;
}
return 0;
}
// I tried to make AI not activating it when there is already an activation card on stack as it will waste all it's mana - several activations even when there is
// only 1 counter to remove - (it is the code I have disabled both in card and it's legacy) it works for parent card itself but I never managed to do it for the legacy card at all.
// For parent card unfortunately when this code is inserted it makes legacy card being activated only sometimes and I can't find out why this happens.
int card_infinite_hourglass(int player, int card, event_t event){
/* Infinite Hourglass |4
* Artifact
* At the beginning of your upkeep, put a time counter on ~.
* All creatures get +1/+0 for each time counter on ~.
* |3: Remove a time counter from ~. Any player may activate this ability but only during any upkeep step. */
upkeep_trigger_ability(player, card, event, player);
if( event == EVENT_UPKEEP_TRIGGER_ABILITY )
add_counter(player, card, COUNTER_TIME);
int amount = count_counters(player, card, COUNTER_TIME);
boost_creature_type(player, card, event, -1, amount, 0, 0, BCT_INCLUDE_SELF);
if( event == EVENT_STATIC_EFFECTS && affect_me(player, card) ){
int found[2] = {-1, -1};
CYCLE_ALL_CARDS(p, c, {
if( in_play(p, c) && is_what(p, c, TYPE_EFFECT) ){
card_instance_t *inst = get_card_instance(p, c);
if( inst->info_slot == (int)infinite_hourglass_legacy ){
if( inst->targets[0].player == player && inst->targets[0].card == card ){
found[p] = c;
}
}
}
};
);
if( found[player] > -1 ){
kill_card(player, found[player], KILL_EXILE);
}
if( found[1-player] == -1 ){
int fake = add_card_to_hand(1-player, get_card_instance(player, card)->internal_card_id);
int legacy = create_legacy_activate(1-player, fake, &infinite_hourglass_legacy);
card_instance_t *inst = get_card_instance(1-player, legacy);
inst->targets[0].player = player;
inst->targets[0].card = card;
inst->number_of_targets = 1;
obliterate_card(1-player, fake);
}
}
/*if (event == EVENT_STATIC_EFFECTS && stack_size == 0)
get_card_instance(player, card)->info_slot = 0;*/
if (!IS_GAA_EVENT(event)){
return 0;
}
int rval = generic_activated_ability(player, card, event, GAA_ONLY_ON_UPKEEP, MANACOST_X(3), 0, NULL, NULL);
if( event == EVENT_CAN_ACTIVATE){
if (current_phase != PHASE_UPKEEP
|| count_counters(player, card, COUNTER_TIME) <= 0)
//|| !has_mana_for_activated_ability(player, card, MANACOST_X(3))
// || get_card_instance(player, card)->info_slot & 1
return 0;
if( rval && IS_AI(player) && count_counters(player, card, COUNTER_TIME) > 0 ){
if( count_subtype(player, TYPE_CREATURE, -1)+1 < count_subtype(1-player, TYPE_CREATURE, -1) ){
EXE_DWORD(0x736808) |= 3;// Force AI to activate. (Same as allowing it to.)
}
}
}
/* if (event == EVENT_ACTIVATE)
get_card_instance(player, card)->info_slot |= 1; */
if (event == EVENT_RESOLVE_ACTIVATION){
remove_counter(player, card, COUNTER_TIME);
}
return rval;
}
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 27 Nov 2020, 19:29
Funny that this came up now. I was just looking at the Shandalar implementation of Sagas and Korath uses a function to check if the triggering saga has an activation on the stack (Sagas aren't sacrificed due to having more counters than their final chapter if they have an activation on the stack); I was thinking that we need a function like that - shouldn't be too difficult to do at all.
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
drool66 - Programmer
- Posts: 1163
- Joined: 25 Nov 2010, 22:38
- Has thanked: 186 times
- Been thanked: 267 times
[DONE]Krovikan Sorcerer 2nd ability tracks all drawn cards t
by Aswan jaguar » 05 Dec 2020, 14:20
Krovikan Sorcerer I have an issue with 2nd ability to limit tracked cards only to the ones drawn with it's ability and not to all cards drawn this turn which my current code does.
- Code: Select all
// in progress keeps track of all cards drawn this turn instead of the ones drawn with it's 2nd ability to discard one of them.
int card_krovikan_sorcerer(int player, int card, event_t event){
/* Krovikan Sorcerer |2|U
* Creature - Human Wizard 1/1
* |T, Discard a non|Sblack card: Draw a card.
* |T, Discard a |Sblack card: Draw two cards, then discard one of them. */
if( ! IS_GAA_EVENT(event) ){
return 0;
}
if( event == EVENT_DRAW_PHASE ){
if(current_turn == player){
get_card_instance(player, card)->info_slot = 3;
}
}
card_instance_t* instance = get_card_instance(player, card);
enum{
CHOICE_DISCARD_NON_BLACK = 1,
CHOICE_DISCARD_BLACK
};
if( event == EVENT_CAN_ACTIVATE ){
return generic_activated_ability(player, card, event, GAA_UNTAPPED | GAA_DISCARD, MANACOST0, 0, NULL, NULL);
}
if( event == EVENT_ACTIVATE ){
test_definition_t this_test;
new_default_test_definition(&this_test, TYPE_ANY, get_sleighted_color_text(player, card, "Select a non %s ", COLOR_BLACK));
this_test.color = get_sleighted_color_test(player, card, COLOR_TEST_BLACK);
this_test.color_flag = DOESNT_MATCH;
this_test.zone = TARGET_ZONE_HAND;
test_definition_t this_test2;
new_default_test_definition(&this_test2, TYPE_ANY, get_sleighted_color_text(player, card, "Select a %s ", COLOR_BLACK));
this_test2.color = get_sleighted_color_test(player, card, COLOR_TEST_BLACK);
this_test2.color_flag = MATCH;
this_test2.zone = TARGET_ZONE_HAND;
int dis_non_black = check_battlefield_for_special_card(player, card, player, 0, &this_test);
int dis_black = check_battlefield_for_special_card(player, card, player, 0, &this_test2);
int choice = DIALOG(player, card, event, DLG_RANDOM,
"Discard a non-black card", dis_non_black, 5,
"Discard a black card", dis_black, 10
);
if( ! choice ){
spell_fizzled = 1;
return 0;
}
instance->info_slot = choice;
if( choice == CHOICE_DISCARD_NON_BLACK){
if( generic_activated_ability(player, card, event, GAA_UNTAPPED, MANACOST0, 0, NULL, NULL) ){
int result = new_select_a_card(player, player, TUTOR_FROM_HAND, 0, AI_MIN_VALUE, -1, &this_test);
if( result != -1){
discard_card(player, result);
}
else{
spell_fizzled = 1;
}
}
}
if( choice == CHOICE_DISCARD_BLACK){
if( generic_activated_ability(player, card, event, GAA_UNTAPPED, MANACOST0, 0, NULL, NULL) ){
int result2 = new_select_a_card(player, player, TUTOR_FROM_HAND, 0, AI_MIN_VALUE, -1, &this_test2);
if( result2 != -1){
discard_card(player, result2);
}
else{
spell_fizzled = 1;
}
}
}
}
if( event == EVENT_RESOLVE_ACTIVATION ){
if( instance->info_slot == CHOICE_DISCARD_NON_BLACK){
draw_cards(player, 1);
}
if( instance->info_slot == CHOICE_DISCARD_BLACK){
draw_cards(player, 2);
int dtt[2][100] = {{0}};
int dtt_count = 0;
int i;
for(i=0; i<active_cards_count[player]; i++){
if( in_hand(player, i) && check_state(player, i, STATE_DRAWN_THIS_TURN) ){
dtt[0][dtt_count] = get_card_instance(player, i)->internal_card_id;
dtt[1][dtt_count] = i;
dtt_count++;
}
}
test_definition_t this_test;
new_default_test_definition(&this_test, TYPE_ANY, "Select a card to discard.");
while( dtt_count > -1 ){
int selected = select_card_from_zone(player, player, dtt[0], dtt_count, 0, AI_MIN_VALUE, -1, &this_test);
if( selected != -1 ){
discard_card(player, dtt[1][selected]);
}
dtt_count--;
break;
}
}
}
return 0;
}
Last edited by Aswan jaguar on 16 Dec 2020, 16:40, edited 1 time in total.
Reason: done
Reason: done
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 10 Dec 2020, 23:57
Two ways I can think of that might work - the first would be to use TRIGGER_CARD_DRAWN. Take a look at gatecrash.c > coerced_confession() - it creates a legacy that uses XTRIGGER_MILLED to find the cards milled. You could do the same with TRIGGER_CARD_DRAWN, except without the legacy so you can load the cards to your array within the card's function - it would conceptually look like this:
- Code: Select all
int array[2][128]; //that will cover six Thought Reflections, or you could do [500] to match the size of Deck[]
if( trigger_condition == TRIGGER_CARD_DRAWN && affect_me(player, card) && reason_for_trigger_controller == player && instance->info_slot == 66 ){
int i;
for(i=0;i<128 && i != -1;i++){
if( i == -1 ){
array[0][i] = get_card_instance(trigger_cause_controller, trigger_cause)->internal_card_id;
array[1][i] = trigger_cause;
break; //should also be covered by the i != -1 part
}
}
cant_be_responded_to = 1 //this isn't an actual trigger
}
then...
if( instance->info_slot == CHOICE_DISCARD_BLACK){
memset(array, -1, sizeof array);
int old_info_slot = intance->info_slot;
intance->info_slot = 66;
draw_cards(player, 2);
intance->info_slot = old_info_slot;
...and now the cards drawn are loaded into array[][]
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
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
by Aswan jaguar » 11 Dec 2020, 16:23
I tried few times with TRIGGER_CARD_DRAWN method that you suggested but I can't find how to use it properly so I didn't even manage to make it to load select_card_from_zone().
---
Trying to squash some bugs and playtesting.
Trying to squash some bugs and playtesting.
-
Aswan jaguar - Super Tester Elite
- Posts: 8079
- Joined: 13 May 2010, 12:17
- Has thanked: 730 times
- Been thanked: 458 times
Re: Card Development - talk about cards code here
by drool66 » 14 Dec 2020, 03:15
Ok, I got it to work for Liliana, Untouched by Death. The semantics are a little different but I'll take a crack at it once I'm done with M19 if you don't mind.
The latest images for Manalink will be here.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
The latest Manalink installation directory will be here. Well, not quite, anymore. Check the latest patches.
-
drool66 - Programmer
- Posts: 1163
- Joined: 25 Nov 2010, 22:38
- Has thanked: 186 times
- Been thanked: 267 times
Who is online
Users browsing this forum: No registered users and 4 guests