Compare commits

...

1193 Commits

Author SHA1 Message Date
Michael Kamensky
636020d870 [maven-release-plugin] prepare release forge-1.6.40 2021-04-24 08:25:57 +03:00
Michael Kamensky
3150aa2e7c - Prepare Forge for Android publish 1.6.40.001 [incremental]. 2021-04-24 08:13:13 +03:00
swordshine
ce22449f41 Merge branch 'shadrix' into 'master'
Shadrix Silverquill: placer is target

See merge request core-developers/forge!4588
2021-04-24 03:04:02 +00:00
swordshine
1c938b5d29 Merge branch 'arix' into 'master'
Arixmethes: always remove counter

See merge request core-developers/forge!4590
2021-04-24 03:03:39 +00:00
swordshine
0493104beb Merge branch 'master' into 'master'
update translations

See merge request core-developers/forge!4591
2021-04-24 03:02:56 +00:00
CCTV-1
9786d9f927 update cmc translation,add missing translation. 2021-04-24 10:39:37 +08:00
CCTV-1
73e5254e2b add missing string 2021-04-24 10:38:51 +08:00
tool4EvEr
7c934d29ac always remove counter 2021-04-23 23:41:24 +02:00
tool4EvEr
39729cb3da placer is target 2021-04-23 22:00:40 +02:00
Michael Kamensky
aadfdac4c5 Merge branch 'Williams-master-patch-36117' into 'master'
SLD Dr Lair Secretorium Super Drop

See merge request core-developers/forge!4583
2021-04-23 19:17:23 +00:00
John
2e358116c2 Update Secret Lair Drop Series.txt 2021-04-23 14:50:59 +00:00
swordshine
71d88523b2 Merge branch 'update_japanese_localization_mana_value' into 'master'
Update Mana Value in Japanese Localization

See merge request core-developers/forge!4582
2021-04-23 10:12:14 +00:00
swordshine
65e702aed7 Merge branch 'patch' into 'master'
More cleanup

See merge request core-developers/forge!4585
2021-04-23 10:07:58 +00:00
sowrdshine
99bb45d96a More cleanup 2021-04-23 18:07:04 +08:00
swordshine
4f41be641a Merge branch 'patch' into 'master'
Fixed Leyline Invocation

See merge request core-developers/forge!4584
2021-04-23 09:18:45 +00:00
sowrdshine
5ab030242b Fixed Leyline Invocation 2021-04-23 17:17:34 +08:00
John
736d9bbd98 Update Secret Lair Drop Series.txt 2021-04-23 08:23:48 +00:00
Lyu Zong-Hong
6295ebdb09 Update Mana Value in Japanese Localization 2021-04-23 13:35:44 +09:00
Michael Kamensky
380a27b83d Merge branch 'update_in_game_text' into 'master'
Update in-game text and also English and Japanese localization (CMC -> mana value and etc.)

See merge request core-developers/forge!4581
2021-04-23 04:30:06 +00:00
Michael Kamensky
691af037a4 Merge branch 'phoenix' into 'master'
Retriever Phoenix: Fix check

See merge request core-developers/forge!4577
2021-04-23 04:29:03 +00:00
Michael Kamensky
70cb78a67d Merge branch 'scrollmenu' into 'master'
Support scrolling when abilities Popup has too many entries

See merge request core-developers/forge!4567
2021-04-23 04:28:50 +00:00
Michael Kamensky
4bd5fe0178 Merge branch 'changetargetsai' into 'master'
ChangeTargetsAI: Improve decision making

See merge request core-developers/forge!4569
2021-04-23 04:27:57 +00:00
Michael Kamensky
babd80edd9 Merge branch 'update_oracle_stx' into 'master'
Update Oracle after STX release

See merge request core-developers/forge!4564
2021-04-23 04:27:30 +00:00
Alumi
74e4efba52 Update Oracle after STX release 2021-04-23 04:27:27 +00:00
Lyu Zong-Hong
2d7e88a8c2 Update in-game text and also English and Japanese localization 2021-04-23 11:01:34 +09:00
Anthony Calosa
d82c8d466a Merge branch 'master' into 'master'
update check

See merge request core-developers/forge!4580
2021-04-23 01:15:12 +00:00
Sol
9edbbf399f Merge branch 'esix' into 'master'
Esix: Script typo

See merge request core-developers/forge!4575
2021-04-23 01:13:11 +00:00
Anthony Calosa
345316b9b0 update check 2021-04-23 09:12:02 +08:00
Anthony Calosa
0bb6636613 Merge branch 'master' into 'master'
[Android] add option to use obb directory as assets directory for Forge

See merge request core-developers/forge!4579
2021-04-22 23:14:25 +00:00
Anthony Calosa
1929b75e34 [Android] add option to use obb directory as assets directory for Forge 2021-04-23 06:45:35 +08:00
tool4EvEr
245827825e Fix check 2021-04-22 20:41:36 +02:00
Bug Hunter
fc9f81c5c9 Merge branch 'fixplaymain1' into 'master'
Fix PlayMain1 scripts

See merge request core-developers/forge!4576
2021-04-22 18:15:13 +00:00
tool4EvEr
987dce2643 Fix PlayMain1 scripts 2021-04-22 20:14:31 +02:00
tool4EvEr
4f2abad39d Script typo 2021-04-22 20:03:19 +02:00
tool4EvEr
354000082b Clean up 2021-04-22 18:50:06 +02:00
Michael Kamensky
b8e87604c2 Merge branch 'master' into 'master'
Achievements for STX and C21 by Marek.

See merge request core-developers/forge!4574
2021-04-22 16:36:16 +00:00
Michael Kamensky
49f7ad1281 - Achievements for STX and C21 by Marek. 2021-04-22 19:35:37 +03:00
Michael Kamensky
6731cdcc9d Merge branch 'twosat-master-patch-53531' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4571
2021-04-22 16:25:21 +00:00
Michael Kamensky
5306510ec1 Merge branch 'nacatl' into 'master'
Nacatl War-Pride and support

See merge request core-developers/forge!4572
2021-04-22 16:25:15 +00:00
Northmoc
18d0bf1c7b Add support for keyword: CARDNAME must be blocked by exactly one creature if able. 2021-04-22 12:00:18 -04:00
Northmoc
0b9755405e nacatl_war_pride.txt 2021-04-22 11:58:36 -04:00
Andreas Bendel
8b7a183e63 Update de-DE.properties
CMC is now Mana Value
2021-04-22 15:20:52 +00:00
Tim Mocny
7a094478f3 Merge branch 'typo' into 'master'
teachings_of_the_archaics.txt only 3 cards

See merge request core-developers/forge!4570
2021-04-22 14:07:47 +00:00
Michael Kamensky
bd063c71f8 Merge branch 'fixNPE' into 'master'
Rework ceaseToExist

See merge request core-developers/forge!4559
2021-04-22 11:25:36 +00:00
Michael Kamensky
693ef6c4d3 Merge branch 'countersMove' into 'master'
CountersMoveAI: Improve logic for Ozolith

See merge request core-developers/forge!4568
2021-04-22 05:03:54 +00:00
tool4EvEr
60a02cd2a5 Improve decision making for multiplayer 2021-04-22 00:08:19 +02:00
tool4EvEr
e25c7dac4a Improve logic for Ozolith 2021-04-21 22:32:37 +02:00
tool4EvEr
d918cade67 Hold triggers until all cards are processed 2021-04-21 21:07:47 +02:00
tool4EvEr
982400b74d Clean up 2021-04-21 20:50:13 +02:00
tool4EvEr
c00a384591 Add MouseWheel support 2021-04-21 19:55:14 +02:00
Michael Kamensky
b0e1630fc5 Merge branch 'tokens' into 'master'
update some token lists

See merge request core-developers/forge!4566
2021-04-21 17:52:24 +00:00
tool4EvEr
50ffd7b4a9 Support scrolling when Popup has too many entries 2021-04-21 19:37:05 +02:00
Northmoc
e4e75a5f73 update some token lists 2021-04-21 13:18:27 -04:00
Northmoc
7da4381d38 update some token lists 2021-04-21 13:18:27 -04:00
Hans Mackowiak
ef8076b626 GameEntityCounterTable: fix NPE and filterToRemove 2021-04-21 17:35:41 +02:00
Bug Hunter
ab5f5c1ce9 Merge branch 'yarok' into 'master'
Yarok: Fix staticability check

See merge request core-developers/forge!4565
2021-04-21 15:26:05 +00:00
tool4EvEr
6d442b12a0 Yarok: Fix staticability check 2021-04-21 17:25:42 +02:00
Sol
0019f83e33 Merge branch 'breena' into 'master'
Fix Breena Trigger

See merge request core-developers/forge!4560
2021-04-21 14:13:20 +00:00
Hans Mackowiak
f3011b1790 Merge branch 'fixMana' into 'master'
Fix mana conversion checking wrong card from hand

See merge request core-developers/forge!4561
2021-04-21 04:36:17 +00:00
Hans Mackowiak
ea79f84b71 Merge branch 'fixyarok' into 'master'
Fix Yarok

See merge request core-developers/forge!4562
2021-04-21 04:35:53 +00:00
tool4EvEr
040d6929dc Fix Yarok 2021-04-20 23:15:32 +02:00
tool4EvEr
2fe9c38fff Fix mana conversion checking wrong card from hand 2021-04-20 20:21:39 +02:00
tool4EvEr
256b54b6b5 Fix Breena Trigger 2021-04-20 19:01:57 +02:00
Michael Kamensky
062ad5d92f Merge branch 'esix_fractal_bloom' into 'master'
Add Esix, Fractal Bloom and necessary supports

See merge request core-developers/forge!4557
2021-04-20 16:44:22 +00:00
Michael Kamensky
5806ef248f Merge branch 'ee' into 'master'
Last STX card! elemental_expressionist.txt

See merge request core-developers/forge!4554
2021-04-20 16:44:04 +00:00
Michael Kamensky
257fcf1b3d Merge branch 'elspeth' into 'master'
AI: Fix Elspeth Conquers Death

See merge request core-developers/forge!4552
2021-04-20 16:43:47 +00:00
tool4EvEr
284db016a0 Rework ceaseToExist 2021-04-20 18:28:01 +02:00
tool4EvEr
5e2898f0ae Improve Counter check 2021-04-20 17:29:20 +02:00
Northmoc
207408ae20 elemental_expressionist.txt 2021-04-20 08:59:54 -04:00
Bug Hunter
2843ca9b49 Merge branch 'TRT-master-patch-20042' into 'master'
Update forge-gui/res/cardsfolder/upcoming/detention_vortex.txt

See merge request core-developers/forge!4558
2021-04-20 12:05:08 +00:00
Bug Hunter
efc09d8a3f Update forge-gui/res/cardsfolder/upcoming/detention_vortex.txt 2021-04-20 12:03:43 +00:00
Lyu Zong-Hong
e6714bf45e Add Esix, Fractal Bloom and necessary supports 2021-04-20 20:47:22 +09:00
Michael Kamensky
8049e2c322 Merge branch 'update_japanese_translation' into 'master'
Update Japanese Card Translation

See merge request core-developers/forge!4556
2021-04-20 11:45:46 +00:00
Lyu Zong-Hong
fdb0db2d41 Update Japanese Card Translation 2021-04-20 19:54:12 +09:00
Michael Kamensky
7465031d44 Merge branch 'fixTriggerCardState' into 'master'
TriggerHandler: fix setting CardState for Trigger

See merge request core-developers/forge!4555
2021-04-20 07:17:09 +00:00
Hans Mackowiak
15eb3a421d TriggerHandler: fix setting CardState for Trigger 2021-04-20 08:45:48 +02:00
Michael Kamensky
ec52a07fe9 Merge branch 'clone' into 'master'
Temporary clones: restore Remembered/Imprinted

Closes #1792

See merge request core-developers/forge!4490
2021-04-20 04:00:42 +00:00
Bug Hunter
b7a475cc0a Temporary clones: restore Remembered/Imprinted 2021-04-20 04:00:41 +00:00
Michael Kamensky
aed022d099 Merge branch 'master' into 'master'
[Mobile] Update LibGDX to 1.10.0, Update Mobile Dev to LWJGL3

See merge request core-developers/forge!4548
2021-04-20 03:59:15 +00:00
Michael Kamensky
86b37e7df0 Merge branch 'chaos' into 'master'
Chaos Warp: Fix wrong target

See merge request core-developers/forge!4551
2021-04-20 03:58:15 +00:00
Michael Kamensky
09e9b46efd Merge branch 'cleanup' into 'master'
Fixes - 19 Apr

See merge request core-developers/forge!4553
2021-04-20 03:58:06 +00:00
Hans Mackowiak
90b685c15d Merge branch 'causeRemoval' into 'master'
remove AbilityUtils.getCause

See merge request core-developers/forge!4550
2021-04-20 00:01:50 +00:00
Northmoc
daedffc0da specter_of_the_fens.txt add Flying! 2021-04-19 15:53:25 -04:00
Northmoc
c489f88f77 brainstorm.txt cleanup Descs 2021-04-19 15:50:55 -04:00
tool4EvEr
33779bb6cf AI: Fix Elspeth Conquers Death 2021-04-19 19:56:13 +02:00
tool4EvEr
660806fb7c Fix wrong target 2021-04-19 19:31:46 +02:00
Hans Mackowiak
40a52ed00b remove AbilityUtils.getCause 2021-04-19 18:12:16 +02:00
Michael Kamensky
c216498198 Merge branch 'master' into 'master'
Update cards translations

See merge request core-developers/forge!4540
2021-04-19 13:52:31 +00:00
Michael Kamensky
547c887c0b Merge branch 'master' into 'master'
Add a NPE guard to ChooseGenericEffectAi

See merge request core-developers/forge!4549
2021-04-19 13:51:41 +00:00
Michael Kamensky
dfe23da44d - Add a NPE guard to ChooseGenericEffectAi 2021-04-19 16:51:00 +03:00
Anthony Calosa
a5e8b88e32 [Mobile] Update LibGDX to 1.10.0, Update Mobile Dev to LWJGL3
refer here for LibGDX Migration/Changes: https://libgdx.com/news/2021/04/the-ultimate-migration-guide
2021-04-19 21:41:34 +08:00
Hans Mackowiak
539c94f36f fix descendants path 2021-04-19 15:02:40 +02:00
Churrufli
fce5ae0498 Merge branch 'master' of https://git.cardforge.org/core-developers/forge 2021-04-19 13:09:01 +02:00
Hans Mackowiak
8fbf96098e CountersPutAi: fix NPE in Polukranos 2021-04-19 13:08:19 +02:00
Hans Mackowiak
c8b43fa8ab Kwain: fix Ability Description 2021-04-19 13:08:19 +02:00
Michael Kamensky
9f305347a8 Merge branch 'PolukranosLogicFix' into 'master'
CountersPutAi: fix NPE in Polukranos

See merge request core-developers/forge!4547
2021-04-19 10:37:47 +00:00
Hans Mackowiak
94d826d2a4 CountersPutAi: fix NPE in Polukranos 2021-04-19 10:37:23 +00:00
Hans Mackowiak
118cf70e40 Kwain: fix Ability Description 2021-04-19 10:21:43 +02:00
Bug Hunter
895d8d594b Bishop of Binding: Update script 2021-04-19 09:07:44 +02:00
tool4EvEr
60ce467859 Fix wrong activator 2021-04-19 09:07:44 +02:00
tool4EvEr
6efa58006e Fix getReflectableManaColors 2021-04-19 09:07:43 +02:00
paul_snoops
f253019daf Brawl STX update 2021-04-19 09:07:43 +02:00
Alumi
8600aceb64 Fix STX typos in edition file 2021-04-19 09:07:43 +02:00
tool4EvEr
a358af534a Improve logic for preventing loops 2021-04-19 09:07:43 +02:00
Anthony Calosa
d080676997 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-19 09:07:43 +02:00
Anthony Calosa
b3412fcfb4 fix lukka wayward bonder ability 2021-04-19 09:07:43 +02:00
Adam Pantel
b5e3db279d Mill quick fix 2021-04-19 09:07:43 +02:00
Adam Pantel
a29ca46ca3 Add ChangesZoneAll trigger in GameAction 2021-04-19 09:07:43 +02:00
Adam Pantel
2fe54a28b1 Add TriggerZones 2021-04-19 09:07:43 +02:00
Adam Pantel
6f9bc11078 Costs should trigger Ranar/Laelia 2021-04-19 09:07:43 +02:00
Northmoc
cd5186cd0d jesters_cap.txt + Mandatory 2021-04-19 09:07:43 +02:00
Northmoc
34cdc299b7 + Mandatory to some ChangeZone effects 2021-04-19 09:07:42 +02:00
Northmoc
274a3f78f2 rampaging_cyclops.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
0e31ad96a5 coralhelm_commander.txt + PresentCompare 2021-04-19 09:07:42 +02:00
Northmoc
b191e4d83a beastbreaker_of_bala_ged.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
16c554aa43 angelic_field_marshal.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
80550cd42e battle_brawler.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
3ac28aa279 desperate_castaways.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
f16ea99cbc angelic_voices.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
d4353bd237 woodborn_behemoth.txt CheckSVar -> IsPresent 2021-04-19 09:07:42 +02:00
Northmoc
8efa82abc2 drover_of_the_mighty.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
a5f42f77ad aeronaut_tinkerer.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:42 +02:00
Northmoc
384314d9ab gate_hound.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
56d63a46bf angelic_overseer.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
da00ccff58 court_homunculus.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
313d5ab762 dauntless_dourbark.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
a569029c40 coralhelm_commander.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:41 +02:00
Northmoc
04748cf03f abzan_kin_guard.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
91871fb96d ember_weaver.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
7292a12ff4 nightfire_giant.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
9b04e8ff38 cliffrunner_behemoth.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
b85a2ea6d2 pterodon_knight.txt CheckSVar -> IsPresent 2021-04-19 09:07:41 +02:00
Northmoc
da6c134540 kondas_hatamoto.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
e09da1a600 jund_hackblade.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
5aa1f9863d ashenmoor_cohort.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
1eeb3637e8 villainous_ogre.txt CheckSVar -> IsPresent, AI 2021-04-19 09:07:40 +02:00
Northmoc
79e229467e ereboss_titan.txt CheckSVar -> IsPresent 2021-04-19 09:07:40 +02:00
Northmoc
d483ea7f47 - add SelectPrompt 2021-04-19 09:07:40 +02:00
Northmoc
4e4b31a0d5 bond_of_insight.txt clean up StackDesc and AI 2021-04-19 09:07:40 +02:00
Northmoc
eb38c1ec15 shadrix_silverquill.txt improve TgtPrompts for modes 2021-04-19 09:07:40 +02:00
Northmoc
b33406da4d garrison_sergeant.txt use IsPresent, improve AI 2021-04-19 09:07:40 +02:00
Northmoc
10f4da71b8 gravecrawler.txt fix static and typos, add AI 2021-04-19 09:07:40 +02:00
Michael Kamensky
6d27a8e489 Merge branch 'bishop' into 'master'
Bishop of Binding: Update script

See merge request core-developers/forge!4544
2021-04-19 04:22:11 +00:00
Bug Hunter
c63477c34e Bishop of Binding: Update script 2021-04-19 04:22:10 +00:00
Michael Kamensky
761d1877fa Merge branch 'fixmana' into 'master'
Fix getReflectableManaColors

See merge request core-developers/forge!4537
2021-04-18 14:11:59 +00:00
tool4EvEr
cf4dbf6ed9 Fix wrong activator 2021-04-18 14:11:46 +00:00
tool4EvEr
53e2df8aa2 Fix getReflectableManaColors 2021-04-18 14:11:46 +00:00
Michael Kamensky
07278de968 Merge branch 'brawl' into 'master'
Brawl STX update

See merge request core-developers/forge!4543
2021-04-18 14:11:28 +00:00
paul_snoops
6710c47db0 Brawl STX update 2021-04-18 14:11:17 +00:00
Michael Kamensky
a5cc820ea6 Merge branch 'fix_stx_typos' into 'master'
Fix STX typos in edition file

See merge request core-developers/forge!4541
2021-04-18 14:10:02 +00:00
Alumi
5eb95980bf Fix STX typos in edition file 2021-04-18 14:10:02 +00:00
Michael Kamensky
57cc0e920a Merge branch 'cloneAI' into 'master'
CloneAI: Improve logic for preventing loops

See merge request core-developers/forge!4545
2021-04-18 14:09:12 +00:00
tool4EvEr
902b707b5e Improve logic for preventing loops 2021-04-18 15:32:50 +02:00
Anthony Calosa
98600e9a19 Merge branch 'kevlahnota-master-patch-80770' into 'master'
fix lukka wayward bonder ability

See merge request core-developers/forge!4542
2021-04-18 06:43:21 +00:00
Anthony Calosa
fbf8ac4883 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-18 06:41:45 +00:00
Anthony Calosa
4c7a25407a fix lukka wayward bonder ability 2021-04-18 06:24:02 +00:00
Michael Kamensky
af9359850d Merge branch 'ranar-2' into 'master'
Some Ranar/Laelia fixes

See merge request core-developers/forge!4538
2021-04-18 04:19:44 +00:00
Adam Pantel
abe5638af5 Mill quick fix 2021-04-18 04:19:32 +00:00
Adam Pantel
c45e8a11d7 Add ChangesZoneAll trigger in GameAction 2021-04-18 04:19:32 +00:00
Adam Pantel
62ef84ea07 Add TriggerZones 2021-04-18 04:19:32 +00:00
Adam Pantel
17b5098602 Costs should trigger Ranar/Laelia 2021-04-18 04:19:32 +00:00
Michael Kamensky
272a7afe9e Merge branch 'fix' into 'master'
Fixes - 4/17

See merge request core-developers/forge!4539
2021-04-18 04:17:57 +00:00
Northmoc
9c7fa1a6a8 jesters_cap.txt + Mandatory 2021-04-17 19:34:26 -04:00
Northmoc
38a050dd13 + Mandatory to some ChangeZone effects 2021-04-17 19:32:18 -04:00
Northmoc
377396d77c rampaging_cyclops.txt CheckSVar -> IsPresent 2021-04-17 19:26:39 -04:00
Northmoc
80fc8eddb9 coralhelm_commander.txt + PresentCompare 2021-04-17 19:25:01 -04:00
Northmoc
d9c23ece15 beastbreaker_of_bala_ged.txt CheckSVar -> IsPresent 2021-04-17 19:23:26 -04:00
Northmoc
84a913451f angelic_field_marshal.txt CheckSVar -> IsPresent, AI 2021-04-17 19:19:41 -04:00
Northmoc
59794efc95 battle_brawler.txt CheckSVar -> IsPresent 2021-04-17 19:15:38 -04:00
Northmoc
13977a07e5 desperate_castaways.txt CheckSVar -> IsPresent 2021-04-17 19:15:37 -04:00
Northmoc
b1eb8bd8ec angelic_voices.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:37 -04:00
Northmoc
69043cfd1c woodborn_behemoth.txt CheckSVar -> IsPresent 2021-04-17 19:15:36 -04:00
Northmoc
f34c713e7e drover_of_the_mighty.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:36 -04:00
Northmoc
e180bf21a7 aeronaut_tinkerer.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:35 -04:00
Northmoc
fdaaa4be30 gate_hound.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:35 -04:00
Northmoc
4db445dd3e angelic_overseer.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:34 -04:00
Northmoc
530d4c0b08 court_homunculus.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:34 -04:00
Northmoc
100ce6ded9 dauntless_dourbark.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:33 -04:00
Northmoc
03deb78aab coralhelm_commander.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:33 -04:00
Northmoc
bb220366be abzan_kin_guard.txt CheckSVar -> IsPresent 2021-04-17 19:15:32 -04:00
Northmoc
cd4e4a0892 ember_weaver.txt CheckSVar -> IsPresent 2021-04-17 19:15:32 -04:00
Northmoc
aa42c302aa nightfire_giant.txt CheckSVar -> IsPresent 2021-04-17 19:15:31 -04:00
Northmoc
5128557e4e cliffrunner_behemoth.txt CheckSVar -> IsPresent 2021-04-17 19:15:31 -04:00
Northmoc
a181f5d1db pterodon_knight.txt CheckSVar -> IsPresent 2021-04-17 19:15:31 -04:00
Northmoc
5e3bc6c507 kondas_hatamoto.txt CheckSVar -> IsPresent 2021-04-17 19:15:30 -04:00
Northmoc
b11a9557d7 jund_hackblade.txt CheckSVar -> IsPresent 2021-04-17 19:15:30 -04:00
Northmoc
593e172832 ashenmoor_cohort.txt CheckSVar -> IsPresent 2021-04-17 19:15:29 -04:00
Northmoc
6b62a7fde3 villainous_ogre.txt CheckSVar -> IsPresent, AI 2021-04-17 19:15:29 -04:00
Northmoc
9a80028ec7 ereboss_titan.txt CheckSVar -> IsPresent 2021-04-17 19:15:28 -04:00
Northmoc
1252e22743 - add SelectPrompt 2021-04-17 19:15:28 -04:00
Northmoc
a3a25d9bef bond_of_insight.txt clean up StackDesc and AI 2021-04-17 19:15:27 -04:00
Northmoc
08fa3a5768 shadrix_silverquill.txt improve TgtPrompts for modes 2021-04-17 19:15:27 -04:00
Northmoc
52cd521902 garrison_sergeant.txt use IsPresent, improve AI 2021-04-17 19:15:26 -04:00
Northmoc
6dd5f1991f gravecrawler.txt fix static and typos, add AI 2021-04-17 19:15:26 -04:00
Churrufli
d3d29e03be Update cards translations 2021-04-18 00:07:34 +02:00
Michael Kamensky
2ddc88dd87 Merge branch 'mudfix' into 'master'
Mudslide cleanup

See merge request core-developers/forge!4535
2021-04-17 14:46:22 +00:00
tool4EvEr
f768aa360f Mudslide cleanup 2021-04-17 14:46:07 +00:00
Bug Hunter
5ba12d009b Merge branch 'ringtypo' into 'master'
Replicating Ring: Fix typo

See merge request core-developers/forge!4536
2021-04-17 10:59:02 +00:00
tool4EvEr
6259891312 Replicating Ring: Fix typo 2021-04-17 12:58:14 +02:00
Michael Kamensky
fad59e4a0f Merge branch 'fix' into 'master'
Fixes - 16 Apr

See merge request core-developers/forge!4532
2021-04-17 04:23:48 +00:00
Michael Kamensky
81852fe8c6 Merge branch 'ranar-2' into 'master'
Ranar v2

See merge request core-developers/forge!4534
2021-04-17 04:22:26 +00:00
Adam Pantel
b33e262de6 Ranar v2 2021-04-17 04:22:25 +00:00
Northmoc
7963919b72 dungeon_master.txt rogue "" 2021-04-16 18:48:59 -04:00
Northmoc
e7f87478b4 fix SpellDesc 2021-04-16 18:00:51 -04:00
Northmoc
c6b8d95747 ruthless_winnower.txt tidy up 2021-04-16 17:22:35 -04:00
Anthony Calosa
8468c5ac52 Merge branch 'master' into 'master'
refactor decompression

See merge request core-developers/forge!4533
2021-04-16 19:42:50 +00:00
Northmoc
27d7ae4b78 allow Coalition Honor Guard in MB1 boosters 2021-04-16 15:38:25 -04:00
Anthony Calosa
fffd2afe5e refactor decompression 2021-04-17 03:27:46 +08:00
Anthony Calosa
15ba670d5a Merge remote-tracking branch 'remotes/core/master' 2021-04-17 00:49:09 +08:00
Northmoc
7e7279a642 wandering_archaic_explore_the_vastlands.txt add Controller$ You to CopySpellAbility 2021-04-16 10:34:56 -04:00
Northmoc
1b765e2408 sky_swallower.txt fix GainControl line 2021-04-16 10:32:25 -04:00
Northmoc
db0ae9253e mage_duel.txt remove bad Cost 2021-04-16 10:28:47 -04:00
Bug Hunter
5573218794 Merge branch 'Northmoc-master-patch-13728' into 'master'
Update lorehold_pledgemage.txt

See merge request core-developers/forge!4531
2021-04-16 14:27:55 +00:00
Northmoc
6e38f16c83 mentors_guidance.txt remove bad Cost 2021-04-16 10:26:45 -04:00
Northmoc
4f0d2370ac bold_plagiarist.txt TriggerZone 2021-04-16 10:26:44 -04:00
Tim Mocny
f49f677107 Update lorehold_pledgemage.txt 2021-04-16 14:18:53 +00:00
Michael Kamensky
cbb11ea723 Merge branch 'kardur' into 'master'
Remove Goad from Kardur

See merge request core-developers/forge!4527
2021-04-16 12:44:41 +00:00
Michael Kamensky
566d011e2e Merge branch 'Williams-master-patch-46381' into 'master'
C21 Tokens

See merge request core-developers/forge!4529
2021-04-16 12:43:34 +00:00
Michael Kamensky
1d2a62d06c Merge branch 'svarsStackInstance' into 'master'
StackInstance only store direct SVars

See merge request core-developers/forge!4528
2021-04-16 12:43:19 +00:00
Hans Mackowiak
743163fce7 StackInstance only store direct SVars 2021-04-16 12:43:19 +00:00
Michael Kamensky
afe5333c88 Merge branch 'filter_more_targetable_cards' into 'master'
Filter more cards in getTargetableCards

See merge request core-developers/forge!4513
2021-04-16 12:41:10 +00:00
John
3bf15b5a35 Update Commander 2021.txt 2021-04-16 08:10:23 +00:00
Hans Mackowiak
1c9cfbc4bb Merge branch 'card-fixes' into 'master'
Fix Zurzoth, Akiri

See merge request core-developers/forge!4519
2021-04-16 07:15:29 +00:00
Adam Pantel
65093b0388 Fix Zurzoth, Akiri 2021-04-16 07:15:29 +00:00
Anthony Calosa
e2320ea77c Merge remote-tracking branch 'remotes/core/master' 2021-04-16 13:30:25 +08:00
Michael Kamensky
59f414f707 Merge branch 'fixmp' into 'master'
Fix Multiplayer LTB triggers

Closes #1324

See merge request core-developers/forge!4511
2021-04-16 04:59:35 +00:00
Michael Kamensky
2a2c12cf77 Merge branch 'titan' into 'master'
ChangeZoneTable for Triplicate Titan

See merge request core-developers/forge!4522
2021-04-16 04:58:41 +00:00
Michael Kamensky
5fd34dc967 Merge branch 'twosat-master-patch-34511' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4523
2021-04-16 04:58:26 +00:00
Michael Kamensky
65df6765da Merge branch 'shamans_trance' into 'master'
Add Shaman's Trance

See merge request core-developers/forge!4526
2021-04-16 04:56:57 +00:00
Northmoc
ff9729d27a CombatUtil.java add "quasi-goad" logic 2021-04-15 20:03:44 -04:00
Northmoc
9876741c50 kardur_doomscourge.txt remove Goad effect 2021-04-15 20:02:44 -04:00
Lyu Zong-Hong
a1f69f0732 Add Shaman's Trance 2021-04-16 08:22:37 +09:00
Andreas Bendel
8174b883a0 Update de-DE.properties
added line and translation lblRandomHistoricArchetypeDecks
translated lblInvalidTargetSpecification
2021-04-15 19:38:32 +00:00
tool4EvEr
337065271e ChangeZoneTable for Triplicate Titan 2021-04-15 21:27:31 +02:00
Anthony Calosa
01dd0e7fc4 Merge remote-tracking branch 'remotes/core/master' 2021-04-16 03:15:02 +08:00
Anthony Calosa
f3ee08ab9a Merge branch 'master' into 'master'
add tokenscript

Closes #1803

See merge request core-developers/forge!4521
2021-04-15 19:09:34 +00:00
Anthony Calosa
ba08c09607 fix triplicate titan 2021-04-15 19:09:29 +00:00
Anthony Calosa
aecfd12911 remove accidental commit 2021-04-16 03:07:54 +08:00
Anthony Calosa
c081e48467 fix triplicate titan 2021-04-16 03:05:40 +08:00
tool4EvEr
237b0324c1 Clean up 2021-04-15 20:26:28 +02:00
tool4EvEr
5aaf39f40b LTB triggers for opponents 2021-04-15 20:20:52 +02:00
austinio7116
7a4ed29945 Merge branch 'historicsanctionfix' into 'master'
Historic GA fix

See merge request core-developers/forge!4520
2021-04-15 17:59:59 +00:00
austinio7116
84014ce9b5 Historic GA fix 2021-04-15 18:56:25 +01:00
austinio7116
08c8e84d72 Merge branch 'ldabuildonly2' into 'master'
LDA module build fixes

See merge request core-developers/forge!4476
2021-04-15 16:55:40 +00:00
tool4EvEr
43171879a0 Fix logic 2021-04-15 18:54:58 +02:00
tool4EvEr
2f7a30f920 Fix Multiplayer LTB triggers 2021-04-15 18:54:57 +02:00
Anthony Calosa
27a93b318d Merge branch 'master' into 'master'
update zip decompression

Closes #1802

See merge request core-developers/forge!4518
2021-04-15 15:27:08 +00:00
Anthony Calosa
816bb2008a update zip decompression
closes #1802
2021-04-15 23:18:40 +08:00
Anthony Calosa
8eb430dffd Merge branch 'kevlahnota-master-patch-49827' into 'master'
Update CardArchetypeLDAGenerator.java

See merge request core-developers/forge!4517
2021-04-15 10:08:11 +00:00
Anthony Calosa
641b5af8eb Update CardArchetypeLDAGenerator.java 2021-04-15 10:06:33 +00:00
Anthony Calosa
38ef1e4ed9 Merge branch 'revert-ed5f5d3c' into 'master'
Revert "Update LDAModelGenetrator.java"

See merge request core-developers/forge!4516
2021-04-15 09:48:01 +00:00
Anthony Calosa
123c41b4e3 Revert "Update LDAModelGenetrator.java"
This reverts commit ed5f5d3c6b
2021-04-15 09:47:31 +00:00
Anthony Calosa
7001f17c88 Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4514
2021-04-15 09:42:53 +00:00
Anthony Calosa
1f10191bc7 Merge branch 'kevlahnota-master-patch-10151' into 'master'
Update LDAModelGenetrator.java

See merge request core-developers/forge!4515
2021-04-15 09:42:23 +00:00
Anthony Calosa
ed5f5d3c6b Update LDAModelGenetrator.java 2021-04-15 09:42:04 +00:00
CCTV-1
a03bb7a353 translate new string. 2021-04-15 16:49:58 +08:00
Lyu Zong-Hong
44d4eae8a1 Filter more cards in getTargetableCards 2021-04-15 16:15:59 +09:00
Michael Kamensky
22ffcf5660 Merge branch 'historicLDA' into 'master'
Historic Archetype Random Deck Generation

See merge request core-developers/forge!4488
2021-04-15 06:30:19 +00:00
Michael Kamensky
f40ea6cc8d Merge branch 'rainbow' into 'master'
Fix DelayedTriggers running when card owner left game

See merge request core-developers/forge!4512
2021-04-15 06:30:05 +00:00
Hans Mackowiak
adfcb2b4bf Merge branch 'facedownYedoraMutate' into 'master'
Card: changed some logic with facedown and copy states

See merge request core-developers/forge!4506
2021-04-15 05:59:29 +00:00
Hans Mackowiak
8d42be3bac Card: changed some logic with facedown and copy states 2021-04-15 05:59:28 +00:00
tool4EvEr
e2036a5203 Clean delayedTriggers from player who lost 2021-04-14 22:58:08 +02:00
tool4EvEr
7107f05adb Fix DelayedTriggers running when card owner left game 2021-04-14 22:12:14 +02:00
austinio7116
529fc42b72 Merge branch 'coremaster' into historicLDA
# Conflicts:
#	forge-gui/res/formats/Sanctioned/Historic.txt
2021-04-14 19:09:01 +01:00
Michael Kamensky
7808605a8c Merge branch 'fix' into 'master'
Some fixes

See merge request core-developers/forge!4508
2021-04-14 18:08:07 +00:00
Michael Kamensky
0930013cc2 Merge branch 'card-fixes' into 'master'
Adrix and Nev only applies to you.

See merge request core-developers/forge!4509
2021-04-14 18:07:58 +00:00
Michael Kamensky
51054b70d6 Merge branch 'codie' into 'master'
[C21] Codie tweaks

See merge request core-developers/forge!4507
2021-04-14 18:07:20 +00:00
Michael Kamensky
471fb621e5 Merge branch 'flagbearer' into 'master'
Add Flagbearer

See merge request core-developers/forge!4495
2021-04-14 18:06:56 +00:00
Alumi
1e0c3e7621 Check if chosen targets meets MustTarget requirements in setupTargets() too. (For spell that has multiple targeting SAs) 2021-04-14 18:06:55 +00:00
Northmoc
a75d312770 oversimplify.txt - RememberLKI needed 2021-04-14 13:21:52 -04:00
austinio7116
0fd2ec58be Merge remote-tracking branch 'Core/master' into coremaster 2021-04-14 18:13:19 +01:00
Northmoc
be484a8077 fix oversimplify.txt 2021-04-14 13:07:06 -04:00
Adam Pantel
3d8388ad92 Adrix and Nev only applies to you. 2021-04-14 12:52:00 -04:00
Northmoc
7a5eac3621 support "an opponent" choosing for multiplayer (and add AI hint) 2021-04-14 12:19:31 -04:00
Hans Mackowiak
ce34e937d6 CostExile: fixed exile cost with mana value X 2021-04-14 17:48:42 +02:00
Northmoc
c8646208f0 codie_vociferous_codex.txt add AI hints 2021-04-14 11:39:20 -04:00
Northmoc
3ed83e4f59 codie_vociferous_codex.txt - add ThisTurn$ to delayed trigger - add MayPlay Effect 2021-04-14 11:33:20 -04:00
Northmoc
ebe6a12555 allow no reordering on zones other than library 2021-04-14 11:28:59 -04:00
Northmoc
1727456487 digsite_engineer.txt DB -> AB 2021-04-14 10:37:29 -04:00
swordshine
ef895777ca Merge branch 'master' into 'master'
Card cleanup and Historic update

See merge request core-developers/forge!4505
2021-04-14 10:58:48 +00:00
swordshine
33fa10e9c1 Merge branch 'emblazoned_golem_remove_max_limit' into 'master'
Remove X max limit for Emblazoned Golem

See merge request core-developers/forge!4503
2021-04-14 10:56:51 +00:00
Hythonia
1f9094caec Removed semicolon 2021-04-14 12:43:33 +02:00
Hythonia
9a5c58aae8 Add Strixhaven and Mystical Archives 2021-04-14 12:41:40 +02:00
Hythonia
1b1e6c394d Card cleanup and Historic update 2021-04-14 12:38:17 +02:00
Anthony Calosa
504872ac1e Merge branch 'master' into 'master'
remove extra svar

See merge request core-developers/forge!4504
2021-04-14 10:07:40 +00:00
Anthony Calosa
edc40f4a6c remove extra svar 2021-04-14 18:06:27 +08:00
Lyu Zong-Hong
7b6e08f791 Remove X max limit for Emblazoned Golem 2021-04-14 16:29:57 +09:00
Michael Kamensky
b184db2ee6 Merge branch 'morophon' into 'master'
Add AILogic to Morophon, the Boundless and move two scripts into correct folders

See merge request core-developers/forge!4500
2021-04-14 06:37:04 +00:00
Michael Kamensky
d10bf7fa35 Merge branch 'fixplay' into 'master'
Once Upon A Time: Fix AltCost

Closes #1801

See merge request core-developers/forge!4501
2021-04-14 06:36:54 +00:00
Michael Kamensky
ce20d21f4c Merge branch 'khm_rankings' into 'master'
KHM updated rankings + nonbasic land rankings!

See merge request core-developers/forge!4502
2021-04-14 06:36:47 +00:00
Michael Kamensky
48a35746a0 Merge branch 'c21_11' into 'master'
promise_of_loyalty.txt

See merge request core-developers/forge!4485
2021-04-14 06:33:19 +00:00
swordshine
bf9afd4ba1 Merge branch 'card-fixes' into 'master'
Card fixes

See merge request core-developers/forge!4499
2021-04-14 04:07:13 +00:00
Northmoc
0eb2d59ecc add AI DeckHas 2021-04-13 20:07:13 -04:00
Northmoc
b9bcfef817 promise_of_loyalty.txt 2021-04-13 20:07:13 -04:00
Northmoc
27d01500fa KHM updated rankings + nonbasic land rankings! 2021-04-13 16:39:47 -04:00
paul_snoops
d2ea3e4986 Add AILogic to Morophon, the Boundless and move to scripts into correct folders 2021-04-13 18:07:52 +01:00
Adam Pantel
b0cecebe34 Card fixes 2021-04-13 12:48:30 -04:00
tool4EvEr
f4971589c6 Fix AltCost 2021-04-13 18:26:34 +02:00
Bug Hunter
506d64dc8b Merge branch 'pennant' into 'master'
Team Pennant: Fix typo

See merge request core-developers/forge!4498
2021-04-13 16:18:36 +00:00
tool4EvEr
f56aa6cc29 Fix typo 2021-04-13 18:17:51 +02:00
Bug Hunter
a17ec8c2db Merge branch 'admiral' into 'master'
Azure Fleet Admiral: Fix typo

See merge request core-developers/forge!4497
2021-04-13 15:56:25 +00:00
tool4EvEr
a718986e2d Fix typo 2021-04-13 17:55:57 +02:00
Michael Kamensky
242e0e7fdc Merge branch 'stx_draft_fix' into 'master'
fix:stx booster missing sta and lesson cards.

See merge request core-developers/forge!4493
2021-04-13 13:40:28 +00:00
Anthony Calosa
75f7cb6df7 Merge branch 'master' into 'master'
update cards

See merge request core-developers/forge!4496
2021-04-13 13:16:01 +00:00
Anthony Calosa
cf7d0792a2 update cards 2021-04-13 21:13:46 +08:00
Hans Mackowiak
29d260db9d Cards: use CharacteristicDefining for Alternative Cost for now 2021-04-13 10:32:16 +02:00
austinio7116
d7dfad1fe8 More Historic LDA data 2021-04-13 08:27:41 +01:00
CCTV-1
82f404e627 each draft boosters should be contain 1 sta card and 1 lesson card. 2021-04-13 14:08:56 +08:00
Anthony Calosa
c49b4c45c8 Merge branch 'kevlahnota-master-patch-11851' into 'master'
finalize Commander 2021.txt

See merge request core-developers/forge!4492
2021-04-13 05:11:16 +00:00
Anthony Calosa
39f3feb210 Update Commander 2021.txt 2021-04-13 05:10:27 +00:00
Michael Kamensky
4ed89e9515 Merge branch 'bangchuckersAI' into 'master'
FlipACoinAI: add logic for Goblin Bangchuckers

Closes #1666

See merge request core-developers/forge!4487
2021-04-13 04:40:07 +00:00
Michael Kamensky
0b6989c60c Merge branch 'update_japanese_localization' into 'master'
Update Japanese Localization

See merge request core-developers/forge!4491
2021-04-13 04:39:12 +00:00
Michael Kamensky
a9139c6023 Merge branch 'nametypos' into 'master'
Make Your Mark fixes and some typos

See merge request core-developers/forge!4486
2021-04-13 04:39:00 +00:00
Michael Kamensky
2de580236e Merge branch 'refine' into 'master'
Gifts Ungiven and Intuition: eliminate ChooseCard line

See merge request core-developers/forge!4489
2021-04-13 04:38:32 +00:00
Lyu Zong-Hong
e5d4979f3e Update Japanese Localization 2021-04-13 10:13:51 +09:00
Northmoc
1023cc5cb2 make_your_mark.txt remove unneeded cleanup 2021-04-12 17:15:56 -04:00
Northmoc
094cf9e400 make_your_mark.txt improve Effect behavior + add cleanup 2021-04-12 13:30:33 -04:00
Northmoc
1bc834efe8 add DeckHints 2021-04-12 13:24:32 -04:00
Northmoc
b4b039b2b0 intuition.txt eliminate ChooseCard line 2021-04-12 13:03:54 -04:00
Hans Mackowiak
d8e850673b StaticAbilityContinuous: fix GainsAbilitiesOfDefined 2021-04-12 17:56:42 +02:00
Northmoc
35d7046d69 gifts_ungiven.txt eliminate ChooseCard line 2021-04-12 11:20:36 -04:00
austinio7116
fad87abfea Historic Random Deck Generation initial data 2021-04-12 15:24:45 +01:00
Hans Mackowiak
10d10ff18d FlipACoinAI: add logic for Goblin Bangchuckers 2021-04-12 16:17:47 +02:00
Northmoc
161b99f252 elven_bow.txt remove default/bad params and desc typo 2021-04-12 09:53:58 -04:00
Northmoc
87ff260c9f dwarven_hammer.txt remove default/bad params 2021-04-12 09:51:18 -04:00
Northmoc
cc4dafa400 draugrs_helm.txt remove default/bad params 2021-04-12 09:50:22 -04:00
Northmoc
9d29904ec5 valkyries_sword.txt remove default/bad params 2021-04-12 09:48:55 -04:00
austinio7116
50be4f4ef8 Merge branch 'coremaster' into historicLDA 2021-04-12 14:46:43 +01:00
austinio7116
5f9c05080f Merge remote-tracking branch 'Core/master' into coremaster 2021-04-12 14:46:33 +01:00
Northmoc
bbfcc87a68 giants_amulet.txt tidy up 2021-04-12 09:46:00 -04:00
austinio7116
d7e9510353 Merge branch 'keeptrying' into 'master'
Improved deck naming removing "-" and updated pioneer deckgen data

See merge request core-developers/forge!4484
2021-04-12 13:45:09 +00:00
austinio7116
1a1057f56f Improved deck naming removing "-" and updated pioneer deckgen data 2021-04-12 13:45:09 +00:00
austinio7116
93ff8cfe1b Added Historic Random Deck Generation 2021-04-12 14:36:57 +01:00
Northmoc
4d05e085ef raiders_karve.txt fix TrigDesc 2021-04-12 09:24:25 -04:00
Northmoc
d2d96d2ece make_your_mark.txt 2021-04-12 09:23:59 -04:00
austinio7116
78762cd479 Reverted accidental test change 2021-04-12 13:40:44 +01:00
austinio7116
cdaa37c5fd Initial basic fix for LDA module to ensure it builds without errors - also removed some unneeded imports which the Maven checks were blocking on 2021-04-12 13:39:49 +01:00
austinio7116
76140fb555 Reverted accidental test change 2021-04-12 13:37:54 +01:00
austinio7116
352c5ea3d3 Corrected version in pom 2021-04-12 13:36:01 +01:00
austinio7116
f167d4fa3b Merge branch 'ldabuildonly2' of https://git.cardforge.org/Austin/forge into ldabuildonly2 2021-04-12 13:32:36 +01:00
austinio7116
7528cd3b58 Merge branch 'master' into 'ldabuildonly2'
# Conflicts:
#   forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
2021-04-12 12:31:42 +00:00
austinio7116
cab8d0fb97 Merge branch 'coremaster' into ldabuildonly2
# Conflicts:
#	forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
2021-04-12 13:11:52 +01:00
austinio7116
c7d9c174fd Merge remote-tracking branch 'Core/master' into coremaster 2021-04-12 13:02:52 +01:00
Michael Kamensky
b7787c8cf6 Merge branch 'Williams-master-patch-65541' into 'master'
C21 Revival Experiment

See merge request core-developers/forge!4480
2021-04-12 11:29:27 +00:00
Anthony Calosa
4d218e3624 Merge branch 'master' into 'master'
unused import

See merge request core-developers/forge!4482
2021-04-12 10:22:46 +00:00
Anthony Calosa
89fab45eb7 unused import 2021-04-12 18:21:54 +08:00
Anthony Calosa
9bca338cc2 Merge branch 'kevlahnota-master-patch-58825' into 'master'
Update Commander 2021.txt

See merge request core-developers/forge!4481
2021-04-12 10:16:33 +00:00
Anthony Calosa
36c3c43f86 Merge branch 'master' into 'master'
Simplify ManaConversion scripts, remove Flash/Ice Cave technical debt

See merge request core-developers/forge!4479
2021-04-12 10:14:23 +00:00
Anthony Calosa
d9f762c485 Update Commander 2021.txt 2021-04-12 10:04:33 +00:00
John
3d206d1fb1 Update revival_experiment.txt 2021-04-12 09:46:24 +00:00
John
2a959f23d9 Add new file 2021-04-12 09:44:53 +00:00
Hythonia
bb659d39db ManaConvert keyword display 2021-04-12 10:03:03 +02:00
Hythonia
49cdd4d36d DefinedCost and DefinedManaCost 2021-04-12 10:03:02 +02:00
Hythonia
41d55cf780 Simplify ManaConvertion scripts 2021-04-12 10:03:02 +02:00
Michael Kamensky
5c5c948193 Merge branch 'card-fixes' into 'master'
Moritte type fix

See merge request core-developers/forge!4477
2021-04-12 04:19:37 +00:00
Michael Kamensky
0e32dc982e Merge branch 'unset-cards' into 'master'
Just host cards

See merge request core-developers/forge!4460
2021-04-12 04:19:30 +00:00
Michael Kamensky
9fffdb8cad Merge branch 'mannequin' into 'master'
Makeshift Mannequin: Fix duration

See merge request core-developers/forge!4474
2021-04-12 04:17:47 +00:00
Michael Kamensky
76a1da2d0f Merge branch 'emblazoned_golem' into 'master'
Add Emblazoned Golem

See merge request core-developers/forge!4467
2021-04-12 04:17:33 +00:00
Adam Pantel
ba012a4b0e Moritte type fix 2021-04-11 21:54:28 -04:00
Adam Pantel
09fd212e29 Remove // from names 2021-04-11 21:53:01 -04:00
swordshine
54fd4787fd Merge branch 'kaboom' into 'master'
Kaboom!

See merge request core-developers/forge!4472
2021-04-12 01:04:57 +00:00
Hans Mackowiak
023499a6c0 Player: run checkStaticAbilities before Companion to apply CDA 2021-04-12 02:03:59 +02:00
Lyu Zong-Hong
a8ad7f540b Add DeckHas hint 2021-04-12 08:01:20 +09:00
Adam Pantel
b7ee2c696b Some host cards 2021-04-11 18:35:03 -04:00
austinio7116
4528223dee Initial basic fix for LDA module to ensure it builds without errors - also removed some unneeded imports which the Maven checks were blocking on 2021-04-11 23:13:49 +01:00
austinio7116
cf729db0c0 Merge remote-tracking branch 'Core/master' into coremaster 2021-04-11 23:13:14 +01:00
austinio7116
a4deafa238 Merge branch 'khmldabranch' into 'master'
KHM deck generation

See merge request core-developers/forge!4469
2021-04-11 22:12:02 +00:00
austinio7116
5e88eab617 Modern LDA data 2021-04-11 22:46:47 +01:00
austinio7116
950e51e0fe Improved snow land decision making on LDA deck generator 2021-04-11 22:46:43 +01:00
austinio7116
1c1d743638 Fixes for snow covered lands in LDA deck generation 2021-04-11 22:46:38 +01:00
austinio7116
ffa7e39121 Merge remote-tracking branch 'Core/master' into coremaster 2021-04-11 22:46:01 +01:00
austinio7116
84d6a24446 Modern LDA data 2021-04-11 22:45:09 +01:00
tool4EvEr
91c92d4b64 Fix duration 2021-04-11 21:27:50 +02:00
Bug Hunter
056229e8ee Merge branch 'impetus' into 'master'
Psychic Impetus: Fix missing param

See merge request core-developers/forge!4473
2021-04-11 19:22:12 +00:00
tool4EvEr
c4c5f7ab6b Fix missing param 2021-04-11 21:21:39 +02:00
tool4EvEr
c7ab33f3ab Oracle adjustment 2021-04-11 21:12:48 +02:00
Michael Kamensky
3a3850ec4e Merge branch 'Williams-master-patch-09539' into 'master'
C21 Sproutback Trudge

See merge request core-developers/forge!4455
2021-04-11 19:04:23 +00:00
Michael Kamensky
84ed014909 Merge branch 'new-cards-2' into 'master'
Combat Calligrapher

Closes #1795

See merge request core-developers/forge!4462
2021-04-11 19:03:57 +00:00
Michael Kamensky
39563682f5 Merge branch 'phasedAttachment' into 'master'
GameEntity: when attachment is phased out, treat as not attached

See merge request core-developers/forge!4470
2021-04-11 19:03:26 +00:00
Michael Kamensky
53179f6e3f Merge branch 'triggerCounterPlayerAddedAll' into 'master'
TriggerCounterPlayerAddedAll: new Trigger

See merge request core-developers/forge!4454
2021-04-11 19:03:12 +00:00
Michael Kamensky
b1d5ba6787 Merge branch 'zaffai' into 'master'
Zaffai: Fix trigger

See merge request core-developers/forge!4471
2021-04-11 19:02:56 +00:00
John
361a3d0371 Add new file 2021-04-11 19:01:59 +00:00
tool4EvEr
6b5b38535e Zaffai: Fix trigger 2021-04-11 18:14:31 +02:00
austinio7116
8a4539b19a Improved snow land decision making on LDA deck generator 2021-04-11 16:06:53 +01:00
Hans Mackowiak
f6ee232d9e GameEntity: when attachment is phased out, treat as not attached 2021-04-11 17:00:20 +02:00
austinio7116
2562d28f4f Fixes for snow covered lands in LDA deck generation 2021-04-11 15:50:02 +01:00
austinio7116
f89cfa3ec8 KHM deck generation 2021-04-11 15:23:22 +01:00
Hans Mackowiak
d8b665f64f TriggerCounterPlayerAddedAll: new Trigger that triggers when a player puts more counters on one object 2021-04-11 15:53:32 +02:00
Adam Pantel
d473737ca8 Remove triggers in setup game state 2021-04-11 09:28:16 -04:00
John
52e7cc41e2 Update sproutback_trudge.txt 2021-04-11 13:08:32 +00:00
austinio7116
53978333fb KHM deck generation 2021-04-11 13:59:58 +01:00
Michael Kamensky
183209baa3 Merge branch 'arlinn' into 'master'
Fix Arlinn Kord transform

See merge request core-developers/forge!4458
2021-04-11 12:43:34 +00:00
Michael Kamensky
0df4e1147c Merge branch 'yedora' into 'master'
Yedora: allow morph

See merge request core-developers/forge!4466
2021-04-11 12:43:26 +00:00
Michael Kamensky
7c42b1f845 Merge branch 'stxrank' into 'master'
STX draftsim ranks

See merge request core-developers/forge!4463
2021-04-11 12:41:21 +00:00
Michael Kamensky
129e4762dc Merge branch 'new-cards' into 'master'
Nils, Discipline Enforcer (attacking tax computation)

See merge request core-developers/forge!4457
2021-04-11 12:40:38 +00:00
Michael Kamensky
fc1c5ca6e9 Merge branch 'fixlang' into 'master'
CardTranslation: Add fallback for english only cards

See merge request core-developers/forge!4456
2021-04-11 12:40:19 +00:00
Lyu Zong-Hong
8ae916ac28 Add Emblazoned Golem 2021-04-11 18:56:49 +09:00
tool4EvEr
0ccf9c961b Yedora: allow morph 2021-04-11 10:27:34 +02:00
Bug Hunter
b894e65618 Merge branch 'TRT-master-patch-28302' into 'master'
Update forge-gui/res/cardsfolder/upcoming/dream_strix.txt

See merge request core-developers/forge!4465
2021-04-11 08:25:14 +00:00
Bug Hunter
801d31ed29 Update forge-gui/res/cardsfolder/upcoming/dream_strix.txt 2021-04-11 08:25:03 +00:00
Anthony Calosa
34fb9bb865 Merge branch 'kevlahnota-master-patch-52335' into 'master'
Update Commander 2021.txt

See merge request core-developers/forge!4464
2021-04-11 07:18:17 +00:00
Anthony Calosa
b5201e17ff Update forge-gui/res/editions/Commander 2021.txt 2021-04-11 07:17:16 +00:00
Anthony Calosa
204bbfc6c7 Update forge-gui/res/editions/Commander 2021.txt 2021-04-11 07:12:00 +00:00
Anthony Calosa
692fe0c885 Update Commander 2021.txt 2021-04-11 07:00:32 +00:00
John
3a372179d6 Update sproutback_trudge.txt 2021-04-11 06:51:44 +00:00
Adam Pantel
3de8bd49ad Combat Calligrapher 2021-04-11 01:59:36 -04:00
austinio7116
b9c02d5d99 Merge branch 'ldastream' into 'master'
LDA module

See merge request core-developers/forge!4251
2021-04-11 05:51:11 +00:00
Hans Mackowiak
185c4c09c9 Changed card-based deck generation to archetype based making better use of the new LDA models. Decks can now be selected by archetype with names generated from the source decklists. Archetypes are ordered by popularity. 2021-04-11 05:51:11 +00:00
austinio7116
ad709fd8f0 STX draftsim ranks 2021-04-11 06:47:57 +01:00
Adam Pantel
da7a1f34e1 Nils, Discipline Enforcer 2021-04-11 00:40:27 -04:00
Michael Kamensky
47d2f3b6ad Merge branch 'new-cards-2' into 'master'
C21: Stinging Study

See merge request core-developers/forge!4461
2021-04-11 04:22:59 +00:00
Michael Kamensky
673759f17b Merge branch 'city' into 'master'
Fix originally printed property

Closes #1797

See merge request core-developers/forge!4453
2021-04-11 04:20:44 +00:00
Adam Pantel
0e2d6ac196 C21: Stinging Study 2021-04-11 00:16:00 -04:00
Anthony Calosa
eb99915912 Merge branch 'master' into 'master'
update ImageKeys

See merge request core-developers/forge!4459
2021-04-11 02:22:04 +00:00
Anthony Calosa
501df02260 update ImageKeys 2021-04-11 10:03:56 +08:00
tool4EvEr
d74f6dba0a Fix Arlinn Kord transform 2021-04-10 23:28:11 +02:00
tool4EvEr
b590c6b55a CardTranslation: Add fallback for english only cards 2021-04-10 20:00:48 +02:00
John
175f38f114 Add new file 2021-04-10 16:53:05 +00:00
Michael Kamensky
44f69b2b0b Merge branch 'fixX' into 'master'
Fix Thieving Skydiver 0 kicker

See merge request core-developers/forge!4450
2021-04-10 16:28:25 +00:00
Michael Kamensky
02a1b139bf Merge branch 'lib' into 'master'
Sylvan Library: Add ordering choice

See merge request core-developers/forge!4452
2021-04-10 16:27:52 +00:00
Michael Kamensky
326cf81a0f Merge branch 'Williams-master-patch-30398' into 'master'
C21 Suthro 10/04

See merge request core-developers/forge!4448
2021-04-10 16:27:45 +00:00
John
8952ddc9dc Update witchs_clinic.txt 2021-04-10 15:31:37 +00:00
John
dfe2c6d064 Update veinwitch_coven.txt 2021-04-10 15:29:31 +00:00
John
fea2f30a1c Update veinwitch_coven.txt 2021-04-10 15:28:55 +00:00
tool4EvEr
5d8bd256f1 Fix originally printed property 2021-04-10 17:28:18 +02:00
John
d7f41f4a56 Add new file 2021-04-10 15:28:18 +00:00
John
9169ea6ebf Update trudge_garden.txt 2021-04-10 15:24:54 +00:00
John
20314c8952 Update trudge_garden.txt 2021-04-10 15:24:00 +00:00
John
f262075d2a Update pest_infestation.txt 2021-04-10 15:21:52 +00:00
John
37d9ea103b Update gyome_master_chef.txt 2021-04-10 15:21:20 +00:00
John
ef51f23308 Update ezzaroot_channeler.txt 2021-04-10 15:20:46 +00:00
John
11f75176b8 Update blossoming_bogbeast.txt 2021-04-10 15:15:33 +00:00
John
bd676c7c5c Update blight_mound.txt 2021-04-10 15:05:17 +00:00
tool4EvEr
ccf51356a9 Sylvan Library: Add ordering choice 2021-04-10 16:13:07 +02:00
tool4EvEr
ece32cbe70 Join XCantBe0 restrictions from both parts when combining costs 2021-04-10 13:51:16 +02:00
Bug Hunter
574e74350c Merge branch 'fixdispute' into 'master'
Academic Dispute: Fix optional

See merge request core-developers/forge!4451
2021-04-10 10:41:27 +00:00
tool4EvEr
4665f8c6c6 Academic Dispute: Fix optional 2021-04-10 12:40:32 +02:00
tool4EvEr
a1bdd1fc47 Fix Thieving Skydiver 0 kicker 2021-04-10 12:35:47 +02:00
Bug Hunter
cdcb801b50 Merge branch 'TRT-master-patch-77164' into 'master'
Update forge-gui/res/cardsfolder/upcoming/explosive_welcome.txt

See merge request core-developers/forge!4449
2021-04-10 08:39:38 +00:00
Bug Hunter
5f4bfed2b5 Update forge-gui/res/cardsfolder/upcoming/explosive_welcome.txt 2021-04-10 08:39:23 +00:00
John
cffaa6fef8 Add new file 2021-04-10 07:09:15 +00:00
John
bf2e5392f8 Add new file 2021-04-10 07:08:31 +00:00
John
de6c93b849 Add new file 2021-04-10 07:07:47 +00:00
John
3b6bbe4799 Add new file 2021-04-10 07:06:05 +00:00
John
62250cd41f Add new file 2021-04-10 07:05:08 +00:00
John
69a3963de0 Add new file 2021-04-10 07:04:23 +00:00
John
acefebfcd7 Add new file 2021-04-10 07:03:27 +00:00
John
b8f7d084c7 Add new file 2021-04-10 07:02:42 +00:00
John
fd420b3d81 Add new file 2021-04-10 07:01:26 +00:00
John
bf799869ef Add new file 2021-04-10 07:00:43 +00:00
John
8fc3004349 Add new file 2021-04-10 06:58:06 +00:00
Michael Kamensky
13b22e2047 Merge branch 'c21_9a' into 'master'
C21 - 9 April (more)

See merge request core-developers/forge!4446
2021-04-10 04:14:10 +00:00
Michael Kamensky
2a33ba9f93 Merge branch 'fixstuff' into 'master'
Fix some stuff

See merge request core-developers/forge!4442
2021-04-10 04:13:47 +00:00
Michael Kamensky
ad09233d4c Merge branch 'clash' into 'master'
ClashEffect: Fix triggering for both players

See merge request core-developers/forge!4436
2021-04-10 04:13:29 +00:00
Bug Hunter
2be3a38bb0 ClashEffect: Fix triggering for both players 2021-04-10 04:13:29 +00:00
Michael Kamensky
26467221b3 Merge branch '1759-extra-generated-text-on-auras' into 'master'
Resolve "Extra generated text on Auras"

Closes #1759

See merge request core-developers/forge!4443
2021-04-10 04:13:12 +00:00
Michael Kamensky
dd19ad356a Merge branch 'guardian-archon' into 'master'
[C21] Guardian Archon

See merge request core-developers/forge!4445
2021-04-10 04:12:48 +00:00
Michael Kamensky
4fe91e9a6a Merge branch 'fix_psychic_battle' into 'master'
Fix Psychic Battle in multiplayer game

See merge request core-developers/forge!4447
2021-04-10 04:12:13 +00:00
Michael Kamensky
63d660d152 Merge branch 'noAbilities' into 'master'
Fix NoAbilities failing for basic land types

See merge request core-developers/forge!4424
2021-04-10 04:12:07 +00:00
Lyu Zong-Hong
730ff1758f Fix Psychic Battle in multiplayer game 2021-04-10 10:10:21 +09:00
Northmoc
174099dd1d tempting_contract.txt 2021-04-09 19:58:18 -04:00
Northmoc
f7f49b98ff healing_technique.txt (Suthro) 2021-04-09 19:05:23 -04:00
Northmoc
18f0af1803 guardian_archon.txt 2021-04-09 17:54:04 -04:00
Northmoc
af2a4e1ce4 tidy up ChosenPlayer for DefinedKW 2021-04-09 17:53:49 -04:00
Bug Hunter
a4a58d9a06 Merge branch 'TRT-master-patch-75766' into 'master'
Update forge-gui/res/cardsfolder/upcoming/uvilda_dean_of_perfection_nassari_dean_of_expression.txt

See merge request core-developers/forge!4444
2021-04-09 20:08:23 +00:00
Bug Hunter
861bb5f608 Update forge-gui/res/cardsfolder/upcoming/uvilda_dean_of_perfection_nassari_dean_of_expression.txt 2021-04-09 20:07:32 +00:00
Hans Mackowiak
36c7f1244d Card: fix faulty SpellDescriptions, skip BasicSpells 2021-04-09 20:57:36 +02:00
Michael Kamensky
8e4d07163d Merge branch 'twosat-master-patch-58716' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4440
2021-04-09 18:21:29 +00:00
Michael Kamensky
1af3cd40c0 Merge branch 'c21_9' into 'master'
C21 - 9 April

See merge request core-developers/forge!4441
2021-04-09 18:21:09 +00:00
Michael Kamensky
8e0eb9d110 Merge branch 'c21_8' into 'master'
C21 - 8 April

See merge request core-developers/forge!4434
2021-04-09 18:20:40 +00:00
Northmoc
bf7dbe0098 fain_the_broker.txt 2021-04-09 13:38:58 -04:00
Northmoc
f37d27f803 blot_out_the_sky.txt fix TokenScript 2021-04-09 13:34:25 -04:00
Northmoc
b98d2fbaa4 quicksilver_dagger.txt clean up Descs 2021-04-09 13:34:03 -04:00
Northmoc
181539a424 keen_duelist.txt 2021-04-09 13:01:46 -04:00
Hans Mackowiak
6fe299d7c8 StaticAbilityPanharmonicon: use LKI only for ChangeZone Trigger 2021-04-09 18:56:52 +02:00
Andreas Bendel
585eb23b3c Update de-DE.properties
translated lblLearnALesson
2021-04-09 15:35:14 +00:00
Hans Mackowiak
04940fc4db StaticAbilityPanharmonicon: clean up use matchesValidParam 2021-04-09 17:28:33 +02:00
Hans Mackowiak
75fa1b7fbe Merge branch 'master' into 'master'
C21 Veyran & Panharmonicon rework

See merge request core-developers/forge!4408
2021-04-09 15:15:30 +00:00
Hythonia
cc8e8b2dd1 C21 Veyran & Panharmonicon rework 2021-04-09 15:15:29 +00:00
Northmoc
0b3bbad98f incarnation_technique.txt 2021-04-09 10:13:44 -04:00
Northmoc
9da11e05ee strixhaven_stadium.txt add TriggerZones$ 2021-04-09 09:57:36 -04:00
Northmoc
ecca6f8eda cunning_rhetoric.txt 2021-04-09 09:40:47 -04:00
Northmoc
b5c6f32d1f author_of_shadows.txt 2021-04-09 09:40:46 -04:00
Northmoc
f38c083b05 inkshield.txt 2021-04-09 09:40:46 -04:00
Northmoc
598a9e0918 felisa_fang_of_silverquill.txt (Suthro) 2021-04-09 09:40:45 -04:00
Michael Kamensky
c93c582ed5 Merge branch 'master' into 'master'
Net Deck Archive Updates

See merge request core-developers/forge!4437
2021-04-09 07:13:21 +00:00
Michael Kamensky
010cfb6b35 Merge branch 'lands' into 'master'
Improve AI land handling

See merge request core-developers/forge!4435
2021-04-09 07:13:19 +00:00
Michael Kamensky
b1d95a5fa0 Merge branch 'fixes' into 'master'
Fixes

See merge request core-developers/forge!4433
2021-04-09 07:10:27 +00:00
Michael Kamensky
0d4bf2ea2e Merge branch 'sponsor' into 'master'
Scholarship Sponsor [C21]

See merge request core-developers/forge!4438
2021-04-09 07:10:16 +00:00
Michael Kamensky
d4eb451ba6 Merge branch 'psychic_battle' into 'master'
Add Psychic Battle

See merge request core-developers/forge!4439
2021-04-09 07:09:52 +00:00
Lyu Zong-Hong
9435701d10 Fix bug that when AI cast divide damage as you choose spells, human can't change targetcorrectly 2021-04-09 15:12:13 +09:00
Lyu Zong-Hong
7603943c38 Add Psychic Battle 2021-04-09 14:06:51 +09:00
Northmoc
a959f79bcf scholarship_sponsor.txt 2021-04-08 22:33:26 -04:00
Northmoc
9899d72dce witherbloom_command.txt fix Mill NumCards 2021-04-08 21:27:58 -04:00
Churrufli
4263ea6881 Net Deck Archive Updates 2021-04-09 00:07:21 +02:00
Adam Pantel
ab287858b3 Improve land handling 2021-04-08 16:05:54 -04:00
tool4EvEr
eb6d168d50 Update NoAbilities logic 2021-04-08 20:40:23 +02:00
Michael Kamensky
52b8c4ae5e Merge branch 'master' into 'master'
translate learnLesson message.

See merge request core-developers/forge!4432
2021-04-08 17:53:37 +00:00
tool4EvEr
1c4640d69b Fix NoAbilities failing for basic land types 2021-04-08 19:42:18 +02:00
Northmoc
3827459229 - pic line 2021-04-08 13:20:21 -04:00
Northmoc
aaeb9f9e84 ajanis_aid.txt Rep Eff: CombatDamage -> IsCombat 2021-04-08 13:19:25 -04:00
Northmoc
61acb3e42b jeska_thrice_reborn.txt Rep Eff: CombatDamage -> IsCombat 2021-04-08 13:18:32 -04:00
Northmoc
4a8f299bb3 gifts_ungiven.txt clean up 2021-04-08 12:58:31 -04:00
Northmoc
5530ca94f3 add Mandatory to some searches that need it 2021-04-08 12:46:42 -04:00
Northmoc
61a1b888bd - add ChoiceTitle 2021-04-08 12:43:40 -04:00
Northmoc
a583701319 intuition.txt fix targeting/choosing 2021-04-08 12:35:10 -04:00
Northmoc
57623c710a remove bad ) 2021-04-08 11:12:44 -04:00
Northmoc
93488528de add TriggerZones to Ward 2021-04-08 11:07:54 -04:00
Northmoc
e7a5ffdf5f containment_breach.txt fix ValidTgts 2021-04-08 11:07:16 -04:00
Northmoc
dc56cb382b elspeth_conquers_death.txt improved AI tags 2021-04-08 11:06:26 -04:00
Northmoc
bb22ea3c8f AI logic for Elspeth Conquers Death 2021-04-08 11:05:22 -04:00
CCTV-1
b856ad0374 translate learnLesson message. 2021-04-08 20:07:56 +08:00
Anthony Calosa
3aff3c51a8 Merge branch 'kevlahnota-master-patch-34682' into 'master'
Update CEditorConstructed.java

See merge request core-developers/forge!4431
2021-04-08 11:09:38 +00:00
Anthony Calosa
3e096a25f7 Update CEditorConstructed.java 2021-04-08 11:09:16 +00:00
Anthony Calosa
adb21e4a7e Merge branch 'c21_edition_update' into 'master'
Update C21 Edition file

See merge request core-developers/forge!4430
2021-04-08 11:03:38 +00:00
paul_snoops
3ea268aea7 Update C21 Edition file 2021-04-08 10:05:54 +01:00
Michael Kamensky
567a8913e4 Merge branch 'Williams-master-patch-18379' into 'master'
C21 07/04 Suthro

See merge request core-developers/forge!4426
2021-04-08 08:05:48 +00:00
John
fd89b30192 Update spawning_kraken.txt 2021-04-08 06:30:32 +00:00
John
ce3b327cff Update guardian_augmenter.txt 2021-04-08 06:29:55 +00:00
John
4e5ac4a921 Update curiosity_crafter.txt 2021-04-08 06:29:23 +00:00
Michael Kamensky
b44c1478b8 Merge branch 'c21_7' into 'master'
C21 - 7 April

See merge request core-developers/forge!4422
2021-04-08 04:10:42 +00:00
Michael Kamensky
b0890ad4bf Merge branch 'resolvedThisTurn' into 'master'
Card: add AbilityResolvedThisTurn

See merge request core-developers/forge!4427
2021-04-08 04:09:44 +00:00
Anthony Calosa
83d632e24c Merge branch 'master' into 'master'
update preload itempool

See merge request core-developers/forge!4428
2021-04-08 02:35:07 +00:00
Anthony Calosa
1e5874aed5 update preload itempool 2021-04-08 09:57:26 +08:00
Northmoc
e807d9a3e7 support for playerXCount TotalCommanderCastFromCommandZone 2021-04-07 21:22:21 -04:00
Northmoc
17bb74ff25 commanders_insight.txt 2021-04-07 21:19:44 -04:00
Hans Mackowiak
5c9e2839eb Card: add AbilityResolvedThisTurn 2021-04-08 02:13:45 +02:00
John
d670a761d9 Update paradox_zone.txt 2021-04-07 22:47:42 +00:00
John
b6a3f765a7 Update paradox_zone.txt 2021-04-07 22:42:30 +00:00
John
2ca2ceea5a Update sequence_engine.txt 2021-04-07 22:41:58 +00:00
John
01b4ab512c Update spawning_kraken.txt 2021-04-07 22:41:33 +00:00
John
d18a6c3479 Update theoretical_duplication.txt 2021-04-07 22:40:59 +00:00
John
ab931cdcb4 Update curiosity_crafter.txt 2021-04-07 22:40:03 +00:00
John
7252b0e69a Add new file 2021-04-07 22:39:04 +00:00
John
ab0ed23c83 Add new file 2021-04-07 22:38:21 +00:00
John
18c002094f Add new file 2021-04-07 22:37:04 +00:00
John
d5f73cb633 Add new file 2021-04-07 22:36:15 +00:00
John
c738da065e Add new file 2021-04-07 22:35:30 +00:00
John
a9296d67f7 Add new file 2021-04-07 22:34:40 +00:00
John
78cb417cfc Add new file 2021-04-07 22:34:00 +00:00
John
4bec5f6792 Add new file 2021-04-07 22:33:14 +00:00
John
3da03f66e0 Add new file 2021-04-07 22:31:33 +00:00
Northmoc
e379c676d0 muse_vortex.txt 2021-04-07 16:58:29 -04:00
Bug Hunter
6565494781 Merge branch 'TRT-master-patch-82622' into 'master'
Update forge-gui/res/cardsfolder/upcoming/abundant_harvest.txt

See merge request core-developers/forge!4425
2021-04-07 20:45:09 +00:00
Bug Hunter
aafcd25403 Update forge-gui/res/cardsfolder/upcoming/abundant_harvest.txt 2021-04-07 20:44:20 +00:00
Northmoc
72c4c0007f oversimplify.txt fix Descs 2021-04-07 15:55:36 -04:00
Northmoc
fd1ce7a750 geometric_nexus.txt 2021-04-07 15:53:26 -04:00
Northmoc
fbbdec5e4e oversimplify.txt 2021-04-07 15:33:51 -04:00
Northmoc
443b613513 deekah_fractal_theorist.txt remove default params 2021-04-07 15:05:05 -04:00
Michael Kamensky
2fa44fc0f4 Merge branch 'Williams-master-patch-07577' into 'master'
C21 Discord Contributions 06/04

See merge request core-developers/forge!4410
2021-04-07 18:02:04 +00:00
Northmoc
5ed21c3ce3 deekah_fractal_theorist.txt 2021-04-07 13:16:16 -04:00
Northmoc
f1d52fb530 ruxa_patient_professor.txt (Suthro) 2021-04-07 12:59:37 -04:00
Northmoc
d418ad8369 fractal_harness.txt 2021-04-07 12:46:48 -04:00
Bug Hunter
7b58d8e77e Merge branch 'TRT-master-patch-33908' into 'master'
Update forge-gui/res/cardsfolder/upcoming/fractal_summoning.txt

See merge request core-developers/forge!4423
2021-04-07 16:31:09 +00:00
Bug Hunter
38971c2946 Update forge-gui/res/cardsfolder/upcoming/fractal_summoning.txt 2021-04-07 16:30:10 +00:00
Northmoc
6cef7fd6b6 creative_technique.txt 2021-04-07 11:14:38 -04:00
Northmoc
92ffb0b1e5 rionya_fire_dancer.txt 2021-04-07 10:44:21 -04:00
Northmoc
75f03f1fd1 support for SumCMCGraveyard (Inferno Project) 2021-04-07 10:30:01 -04:00
Northmoc
0306817ac8 inferno_project.txt 2021-04-07 10:29:14 -04:00
Tim Mocny
03b5dc046d Merge branch 'cmc' into 'master'
fix cmc necrotic_fumes.txt

See merge request core-developers/forge!4421
2021-04-07 13:32:42 +00:00
Northmoc
bb5cb8a773 necrotic_fumes.txt 2021-04-07 09:31:40 -04:00
Sol
719bd257a6 Merge branch 'retriever_phoenix_fix' into 'master'
Retriever Phoenix fix for the following error

See merge request core-developers/forge!4420
2021-04-07 12:08:59 +00:00
paul_snoops
e21257ab71 Retriever Phoenix fix for the following error
Caused by: java.lang.RuntimeException: AbilityFactory : getAbility -- Retriever Phoenix has no SVar: TrigLearn
2021-04-07 12:39:41 +01:00
Bug Hunter
fc933ba9d5 Merge branch 'TRT-master-patch-18852' into 'master'
Update forge-gui/res/cardsfolder/upcoming/basic_conjuration.txt

See merge request core-developers/forge!4419
2021-04-07 09:32:28 +00:00
Bug Hunter
9f9215cb3f Update forge-gui/res/cardsfolder/upcoming/basic_conjuration.txt 2021-04-07 09:31:36 +00:00
Anthony Calosa
8308c21d39 Merge branch 'kevlahnota-master-patch-67446' into 'master'
Update StaticData.java

See merge request core-developers/forge!4418
2021-04-07 08:00:16 +00:00
Anthony Calosa
1f6ef5f045 Update StaticData.java 2021-04-07 07:59:39 +00:00
Anthony Calosa
7673918178 Merge branch 'master' into 'master'
Update support for Custom Cards

See merge request core-developers/forge!4417
2021-04-07 07:53:23 +00:00
Anthony Calosa
92b0aad937 Update support for Custom Cards 2021-04-07 15:50:24 +08:00
Michael Kamensky
233e126cd3 Merge branch 'Williams-master-patch-47951' into 'master'
Update Judge Gift Cards 2021.txt

See merge request core-developers/forge!4416
2021-04-07 07:33:50 +00:00
John
e9506787d5 Update Judge Gift Cards 2021.txt 2021-04-07 06:51:51 +00:00
John
e061289a26 Update elementalists_palette.txt 2021-04-07 06:47:38 +00:00
Michael Kamensky
7f87164235 Merge branch 'learnEffect' into 'master'
Learn effect

See merge request core-developers/forge!4374
2021-04-07 05:11:55 +00:00
Hans Mackowiak
0627d37f69 - Basic AI for AF Learn
- Some minor tweaks and fixes for AF Learn
2021-04-07 05:11:55 +00:00
Michael Kamensky
68dc49cb88 Merge branch 'secretly' into 'master'
Emissary of Grudges, Stalking Leonin

See merge request core-developers/forge!4413
2021-04-07 05:11:28 +00:00
Michael Kamensky
24cf97ca84 Merge branch 'cultic' into 'master'
Add Cultic Cube

See merge request core-developers/forge!4411
2021-04-07 05:08:13 +00:00
Michael Kamensky
9859cbdca6 Merge branch 'ConditionDefinedSpells' into 'master'
SpellAbilityCondition: extends ConditionDefined to work with SpellAbilities

See merge request core-developers/forge!4412
2021-04-07 05:07:58 +00:00
Michael Kamensky
b819f303bb Merge branch 'new-cards' into 'master'
C21 Cards

See merge request core-developers/forge!4414
2021-04-07 05:07:18 +00:00
Michael Kamensky
5ad4a25c33 Merge branch 'card-fixes' into 'master'
Some card fixes

See merge request core-developers/forge!4415
2021-04-07 05:07:01 +00:00
Adam Pantel
265e5bb122 Golden Ratio fix 2021-04-06 23:35:18 -04:00
Adam Pantel
7964f862c6 Fixes from discord 2021-04-06 22:49:16 -04:00
Adam Pantel
622176d3de Surge to Victory 2021-04-06 22:43:51 -04:00
Adam Pantel
7e793a48e7 Duration fix on Rowan 2021-04-06 22:18:58 -04:00
Adam Pantel
5bdc4615e8 Reinterpret 2021-04-06 19:31:39 -04:00
Adam Pantel
ef04a4376a Emissary of Grudges, Stalking Leonin 2021-04-06 19:29:37 -04:00
John
3c57007765 Update rousing_refrain.txt 2021-04-06 22:08:21 +00:00
John
9965f2e79c Update inspiring_refrain.txt 2021-04-06 22:06:13 +00:00
John
f131997d6a Update sly_instigator.txt 2021-04-06 22:05:02 +00:00
John
08b9e3c46a Update inspiring_refrain.txt 2021-04-06 22:04:08 +00:00
John
8afbaefa6d Update inspiring_refrain.txt 2021-04-06 22:00:21 +00:00
John
88a8336ae7 Update rousing_refrain.txt 2021-04-06 21:59:41 +00:00
John
0c6dface18 Update rousing_refrain.txt 2021-04-06 21:59:01 +00:00
John
9bb2dd3887 Update inspiring_refrain.txt 2021-04-06 21:49:48 +00:00
John
1f3943fe9a Update octavia_living_thesis.txt 2021-04-06 21:48:59 +00:00
John
9502034d58 Update inspiring_refrain.txt 2021-04-06 21:48:37 +00:00
Hans Mackowiak
c6b0b2021c SpellAbilityCondition: extends ConditionDefined to work with SpellAbilities 2021-04-06 22:13:48 +02:00
John
0d889ec773 Add new file 2021-04-06 19:55:33 +00:00
John
3d8f848936 Add new file 2021-04-06 19:54:52 +00:00
Adam Pantel
6cbd108bca Add Cultic Cube 2021-04-06 15:03:26 -04:00
John
03c950f82e Add new file 2021-04-06 17:58:35 +00:00
John
552b061357 Add new file 2021-04-06 17:47:44 +00:00
John
ca98432e20 Add new file 2021-04-06 17:21:44 +00:00
John
6ada1f04da Add new file 2021-04-06 17:20:43 +00:00
John
edf1738e3c Add new file 2021-04-06 17:18:45 +00:00
Michael Kamensky
5b77205b77 Merge branch 'ranking' into 'master'
Add default cube rankings from cubecobra

See merge request core-developers/forge!4409
2021-04-06 17:09:07 +00:00
Michael Kamensky
67128c2fd5 Merge branch 'new-cards' into 'master'
C21 Cards

See merge request core-developers/forge!4406
2021-04-06 17:08:49 +00:00
Michael Kamensky
490ec7ef3e Merge branch 'Williams-master-patch-25580' into 'master'
C21 Suthro 5/4

See merge request core-developers/forge!4401
2021-04-06 17:08:27 +00:00
Michael Kamensky
0ab7ff919b Merge branch 'new-cards-2' into 'master'
Demonstrate keyword and Excavation Technique

See merge request core-developers/forge!4397
2021-04-06 17:08:12 +00:00
Adam Pantel
489ccd9003 Add default cube rankings from cubecobra 2021-04-06 12:28:29 -04:00
Sol
9971eef9b8 Merge branch 'Williams-master-patch-65571' into 'master'
Update STX edition file

See merge request core-developers/forge!4407
2021-04-06 14:43:38 +00:00
John
88e8bed395 Update Standard.txt 2021-04-06 14:29:27 +00:00
John
9304942fd6 Update Pioneer.txt 2021-04-06 14:29:14 +00:00
John
5f3b5cbe26 Update Modern.txt 2021-04-06 14:28:42 +00:00
John
c5dbc8ddca Update Strixhaven School of Mages.txt 2021-04-06 14:07:29 +00:00
John
eaea041e73 Update blocks.txt 2021-04-06 14:04:16 +00:00
John
20b4ae230f Update Strixhaven School of Mages.txt 2021-04-06 14:02:56 +00:00
Adam Pantel
037dce8be8 Wake the Past 2021-04-06 09:59:08 -04:00
Anthony Calosa
3ad6045377 Merge branch 'master' into 'master'
update AssetsDownloader

See merge request core-developers/forge!4405
2021-04-06 12:18:40 +00:00
Anthony Calosa
3f683c3909 update AssetsDownloader 2021-04-06 20:16:45 +08:00
Anthony Calosa
2f9974f6ef Merge branch 'master' into 'master'
fix downloader

See merge request core-developers/forge!4404
2021-04-06 11:25:47 +00:00
Anthony Calosa
d2156a1179 Merge remote-tracking branch 'core/master' 2021-04-06 19:24:33 +08:00
Anthony Calosa
d6a0b92f1e fix downloader 2021-04-06 19:21:45 +08:00
Bug Hunter
15aab58415 Merge branch 'TRT-master-patch-76250' into 'master'
Update forge-gui/res/cardsfolder/upcoming/culling_ritual.txt

See merge request core-developers/forge!4403
2021-04-06 10:40:56 +00:00
Bug Hunter
1e8679bf94 Update forge-gui/res/cardsfolder/upcoming/culling_ritual.txt 2021-04-06 10:39:39 +00:00
Michael Kamensky
4eeb4c515f Merge branch 'fixtarget' into 'master'
DestroyAI: Fix illegal targets

See merge request core-developers/forge!4394
2021-04-06 08:57:17 +00:00
John
76a75942a9 Update triplicate_titan.txt 2021-04-06 08:47:17 +00:00
John
4383c3486a Update digsite_engineer.txt 2021-04-06 08:46:53 +00:00
Anthony Calosa
e629877984 Merge branch 'master' into 'master'
fix filteredCards, fix NPE devmode

See merge request core-developers/forge!4402
2021-04-06 07:53:23 +00:00
Anthony Calosa
fb71ea83f0 Merge remote-tracking branch 'core/master' 2021-04-06 15:50:39 +08:00
Anthony Calosa
d9cf090060 update 2021-04-06 15:49:30 +08:00
Michael Kamensky
a0fd16513a Merge branch 'c21_5' into 'master'
C21 - 5 April

See merge request core-developers/forge!4396
2021-04-06 07:30:26 +00:00
Michael Kamensky
c7c6d4e162 Merge branch 'new-cards' into 'master'
Strict Proctor

See merge request core-developers/forge!4393
2021-04-06 07:27:15 +00:00
Anthony Calosa
9a2eaa7a1d fix filteredCards, fix NPE devmode 2021-04-06 15:27:14 +08:00
Michael Kamensky
3b12843d5f Merge branch 'card-fixes' into 'master'
Typo in Liliana's Scorn

See merge request core-developers/forge!4398
2021-04-06 07:25:20 +00:00
Michael Kamensky
6685649e39 Merge branch 'AIplayCantBeCast' into 'master'
AI: Add missing CantBeCast check from PlayEffect

See merge request core-developers/forge!4391
2021-04-06 07:23:55 +00:00
John
7e63c902d3 Update triplicate_titan.txt 2021-04-06 06:50:50 +00:00
John
c1e1530cd9 Update losheel_clockwork_scholar.txt 2021-04-06 06:49:52 +00:00
John
b1a190093c Update digsite_engineer.txt 2021-04-06 06:49:01 +00:00
Hans Mackowiak
fcc7cf0566 Merge branch 'kevlahnota-master-patch-51370' into 'master'
Update lathiel_the_bounteous_dawn.txt

See merge request core-developers/forge!4400
2021-04-06 06:47:33 +00:00
Anthony Calosa
ca3d51157f Update lathiel_the_bounteous_dawn.txt 2021-04-06 06:47:33 +00:00
John
899a7b2500 Update bronze_guardian.txt 2021-04-06 06:46:58 +00:00
John
d19be1731e Update angel_of_ruins.txt 2021-04-06 06:45:26 +00:00
John
90768b6933 Update alibou_ancient_witness.txt 2021-04-06 06:43:39 +00:00
John
89d6f050d1 Add new file 2021-04-06 06:40:06 +00:00
John
5bfe7a2146 Add new file 2021-04-06 06:39:07 +00:00
John
e54dc157ac Add new file 2021-04-06 06:38:17 +00:00
John
5be7472dd9 Add new file 2021-04-06 06:37:16 +00:00
John
1a2de2bbc2 Add new file 2021-04-06 06:36:01 +00:00
John
0c9935b1b5 Add new file 2021-04-06 06:34:55 +00:00
John
aba00ad17b Add new file 2021-04-06 06:33:04 +00:00
Anthony Calosa
5ca05394d6 Merge branch 'kevlahnota-master-patch-58460' into 'master'
Update ImageCache.java

See merge request core-developers/forge!4399
2021-04-06 03:59:42 +00:00
Anthony Calosa
5165415fb1 Update ImageCache.java
Don't dispose defaultImage
2021-04-06 03:59:21 +00:00
Adam Pantel
1f1060b97c Typo in Liliana's Scorn 2021-04-05 23:50:37 -04:00
Adam Pantel
3e9dabe2a5 Demonstrate keyword and Excavation Technique 2021-04-05 23:19:33 -04:00
Northmoc
86d72b15c5 support for archaeomancers_map.txt 2021-04-05 23:05:56 -04:00
Northmoc
3fd3a3e5aa archaeomancers_map.txt 2021-04-05 23:05:16 -04:00
Northmoc
3fbcf09326 ruin_grinder.txt 2021-04-05 23:04:47 -04:00
Anthony Calosa
d97cd9eabe Merge branch 'STX_edition' into 'master'
STX edition missing cards

See merge request core-developers/forge!4392
2021-04-06 01:32:58 +00:00
Sol
de52bb1036 Merge branch 'card-fixes' into 'master'
MoJhoSto improvements

See merge request core-developers/forge!4395
2021-04-06 01:19:38 +00:00
Adam Pantel
f9a073fd93 Momir script improvement 2021-04-05 19:32:51 -04:00
Adam Pantel
c77f99cf9b MoJhoSto logging 2021-04-05 18:57:36 -04:00
Northmoc
af8c1b2092 cursed_mirror.txt 2021-04-05 18:45:59 -04:00
Adam Pantel
9095dcd9d8 Add infinite loop protection for Stonehewer 2021-04-05 18:21:32 -04:00
Northmoc
08359103da teachings_of_the_archaics.txt only 3 cards 2021-04-05 18:15:36 -04:00
Northmoc
9fe15fd8c2 audacious_reshapers.txt 2021-04-05 18:11:18 -04:00
Northmoc
728afbc887 madcap_experiment.txt tidy up 2021-04-05 18:10:51 -04:00
Adam Pantel
1e10d49d0f Strict Proctor 2021-04-05 17:51:26 -04:00
tool4EvEr
4a81ea0ee4 Fix illegal targets 2021-04-05 23:50:16 +02:00
Northmoc
acf89b1e9e monologue_tax.txt 2021-04-05 17:41:01 -04:00
paul_snoops
1c99b5bbc7 STX edition missing cards 2021-04-05 22:11:33 +01:00
Northmoc
d8d0c67462 study_hall.txt 2021-04-05 16:29:22 -04:00
Michael Kamensky
2e9f050707 Merge branch 'stx_5' into 'master'
STX - Conspiracy Theorist

See merge request core-developers/forge!4387
2021-04-05 19:29:30 +00:00
Michael Kamensky
4b0a3741de Merge branch 'livio' into 'master'
livio_oathsworn_sentinel.txt fixup

See merge request core-developers/forge!4390
2021-04-05 19:28:27 +00:00
Michael Kamensky
c45c5f6e92 Merge branch 'new-cards-2' into 'master'
Dragon's Approach

See merge request core-developers/forge!4363
2021-04-05 19:26:54 +00:00
Michael Kamensky
482515f538 Merge branch 'fixdig' into 'master'
Fix DigMultiple not changing cards when no valid targets

See merge request core-developers/forge!4386
2021-04-05 19:26:16 +00:00
tool4EvEr
42d75bd1ee Add missing CantBeCast check 2021-04-05 21:06:52 +02:00
Northmoc
296d7436ae fix stonehewer_giant_avatar.txt 2021-04-05 12:38:39 -04:00
Northmoc
5ba1466b96 livio_oathsworn_sentinel.txt fixup 2021-04-05 12:02:43 -04:00
Sol
7cdf36df87 Merge branch 'STX_fixes' into 'master'
STX edition fix MDFCs

See merge request core-developers/forge!4389
2021-04-05 14:57:07 +00:00
paul_snoops
02f25f8816 STX edition fix MDFCs 2021-04-05 15:54:13 +01:00
Sol
fa7e6bfa36 Merge branch 'stx_update' into 'master'
STX edition file update

See merge request core-developers/forge!4367
2021-04-05 14:07:13 +00:00
paul_snoops
854890cc77 STX edition file update 2021-04-05 14:57:15 +01:00
Northmoc
5445ec1b8d RememberTriggerCards param support for Conspiracy Theorist 2021-04-05 08:44:40 -04:00
Northmoc
050f11bfab conspiracy_theorist.txt 2021-04-05 08:43:46 -04:00
tool4EvEr
da89b95654 Fix DigMultiple not changing cards when no valid targets 2021-04-05 13:41:46 +02:00
Anthony Calosa
71631ccf0c Merge branch 'churrufli-master-patch-15537' into 'master'
Update text, resource file size

See merge request core-developers/forge!4384
2021-04-05 10:24:05 +00:00
Churrufli
f1c38dd742 Update text 2021-04-05 10:01:34 +00:00
Bug Hunter
0fc7126238 Merge branch 'typofix' into 'master'
Show of Confidence: Fix cost typo

See merge request core-developers/forge!4383
2021-04-05 09:45:53 +00:00
tool4EvEr
41cf366014 Fix cost typo 2021-04-05 11:45:13 +02:00
Anthony Calosa
4288ae3582 Merge branch 'master' into 'master'
[Mobile] Seperate cache for Cards and Other Images

See merge request core-developers/forge!4382
2021-04-05 09:07:03 +00:00
Anthony Calosa
e57d0a4d0e [Mobile] add OtherImageLoader for otherCache 2021-04-05 17:04:40 +08:00
Anthony Calosa
1b6a0188f3 [Mobile] Seperate cache for Cards/Tokens and Icon/Booster/Fatpacks 2021-04-05 15:45:22 +08:00
Michael Kamensky
e225b8a77c Merge branch 'card-fixes' into 'master'
Codie oracle text

See merge request core-developers/forge!4381
2021-04-05 04:12:40 +00:00
Michael Kamensky
e4b1c542db Merge branch 'sheetfix' into 'master'
Sheets / Edition parsing fixes

See merge request core-developers/forge!4377
2021-04-05 04:12:33 +00:00
Adam Pantel
b960460e02 Codie oracle text 2021-04-04 23:33:31 -04:00
Anthony Calosa
c8f39802ae Merge branch 'master' into 'master'
Limit preloading

See merge request core-developers/forge!4380
2021-04-05 03:27:59 +00:00
Anthony Calosa
e3caad856d Limit preloading 2021-04-05 11:25:42 +08:00
Anthony Calosa
97bc512d1c Merge branch 'master' into 'master'
cleanup

See merge request core-developers/forge!4379
2021-04-05 02:07:51 +00:00
Anthony Calosa
5752fe698f cleanup 2021-04-05 10:06:16 +08:00
Anthony Calosa
727ef87a11 Merge branch 'master' into 'master'
[Mobile] Update ImageCache Texture dispose

See merge request core-developers/forge!4378
2021-04-04 22:56:04 +00:00
Anthony Calosa
a92c85a0be [Mobile] Update ImageCache Texture dispose 2021-04-05 06:51:27 +08:00
tool4EvEr
225790cc4b Fix parsing of new edition sections 2021-04-04 23:52:41 +02:00
tool4EvEr
0202d52e49 Printsheets: Update wrong JMP codes 2021-04-04 22:56:09 +02:00
Bug Hunter
397ad4c597 Merge branch 'brawl2' into 'master'
Add commander relocation for brawl

See merge request core-developers/forge!4376
2021-04-04 20:29:12 +00:00
tool4EvEr
ce00f31f0f Add commander relocation for brawl 2021-04-04 22:28:19 +02:00
swordshine
b9dc9e6eb0 Merge branch 'master' into 'master'
Leonin Vanguard fix + Medomai

See merge request core-developers/forge!4372
2021-04-04 15:33:30 +00:00
Anthony Calosa
05884bba0d Merge branch 'master' into 'master'
[Mobile] When generating a lot of image at once (more than 100) in the shop...

See merge request core-developers/forge!4373
2021-04-04 13:40:36 +00:00
Anthony Calosa
d8f37ac6a7 Merge remote-tracking branch 'core/master' 2021-04-04 21:38:01 +08:00
Anthony Calosa
9c89e0b790 [Mobile] When generating a lot of image at once (more than 100) in the shop list/rewards/unlocked sets, clear the cache 2021-04-04 21:35:01 +08:00
Hythonia
b37d695c8b Minor card fixes 2021-04-04 15:06:50 +02:00
Michael Kamensky
263db54a12 Merge branch 'Williams-master-patch-50535' into 'master'
STX Discord Contributions

See merge request core-developers/forge!4362
2021-04-04 11:23:18 +00:00
Michael Kamensky
dc8c25b8bc Merge branch 'master' into 'master'
STX: Last three cards

See merge request core-developers/forge!4365
2021-04-04 11:18:53 +00:00
Anthony Calosa
1d43cae1b1 Merge branch 'kevlahnota-master-patch-75509' into 'master'
unused import

See merge request core-developers/forge!4371
2021-04-04 10:58:35 +00:00
Anthony Calosa
7e02ae518d unused import 2021-04-04 10:58:14 +00:00
Bug Hunter
4afc439eb1 Merge branch 'Williams-master-patch-14604' into 'master'
Update make_your_mark.txt

See merge request core-developers/forge!4369
2021-04-04 10:39:19 +00:00
Anthony Calosa
330d4f006f Merge branch 'kevlahnota-master-patch-65912' into 'master'
update token-images

See merge request core-developers/forge!4370
2021-04-04 10:23:17 +00:00
Anthony Calosa
5929a7c6f9 Update token-images.txt 2021-04-04 10:19:57 +00:00
Anthony Calosa
1b95989b05 Update token-images.txt 2021-04-04 10:14:59 +00:00
John
b284fc590e Update make_your_mark.txt 2021-04-04 10:13:45 +00:00
Anthony Calosa
dd3e21e5d4 Merge branch 'kevlahnota-master-patch-92499' into 'master'
Update ImageCache.java

See merge request core-developers/forge!4368
2021-04-04 09:29:00 +00:00
Anthony Calosa
31c674c3dc Update ImageCache.java 2021-04-04 09:28:15 +00:00
paul_snoops
b3632417a7 STX edition file update (no tokens yet) 2021-04-04 10:24:35 +01:00
Anthony Calosa
bc28919727 Merge branch 'kevlahnota-master-patch-57420' into 'master'
Update borderlessCardList.txt

See merge request core-developers/forge!4366
2021-04-04 09:12:05 +00:00
Anthony Calosa
4333744248 Update borderlessCardList.txt 2021-04-04 09:11:20 +00:00
John
d62599d62c Add new file 2021-04-04 07:31:19 +00:00
John
bad745d2a9 Add new file 2021-04-04 07:30:02 +00:00
Hythonia
0c6aa6afdb STX: Last three cards 2021-04-04 09:29:37 +02:00
John
bb8e68d1b1 Add new file 2021-04-04 07:29:31 +00:00
John
8c6b6e617c Add new file 2021-04-04 07:29:01 +00:00
John
a9e4270ddb Add new file 2021-04-04 07:28:24 +00:00
John
72262a77fd Add new file 2021-04-04 07:27:47 +00:00
John
68c524a75d Add new file 2021-04-04 07:27:08 +00:00
John
64c56288c9 Add new file 2021-04-04 07:26:25 +00:00
John
2996bf7e6b Add new file 2021-04-04 07:25:46 +00:00
John
5520c3778b Add new file 2021-04-04 07:23:42 +00:00
John
4c6bdcb68a Add new file 2021-04-04 07:23:08 +00:00
John
a925786899 Add new file 2021-04-04 07:22:18 +00:00
John
e73a3cbf8c Add new file 2021-04-04 07:21:26 +00:00
John
c86dbb4665 Add new file 2021-04-04 07:20:24 +00:00
John
951c338edd Add new file 2021-04-04 07:19:29 +00:00
John
5a22ad86b8 Add new file 2021-04-04 07:18:25 +00:00
John
6db22d43a6 Add new file 2021-04-04 07:17:26 +00:00
John
6569f59501 Add new file 2021-04-04 07:16:44 +00:00
John
c30b04131b Add new file 2021-04-04 07:15:58 +00:00
John
fb7e6d482a Add new file 2021-04-04 07:15:13 +00:00
John
df252a0051 Add new file 2021-04-04 07:14:08 +00:00
John
443d79f902 Update springmane_cervin.txt 2021-04-04 07:11:38 +00:00
John
7ac9997950 Update emergent_sequence.txt 2021-04-04 07:10:58 +00:00
John
aa59b5b4e3 Update callous_bloodmage.txt 2021-04-04 07:10:29 +00:00
John
2cd0b56f11 Update acess_tunnel.txt 2021-04-04 07:09:45 +00:00
Michael Kamensky
40c8485f02 Merge branch 'fix_illusionary_mask' into 'master'
Copy SA without mana cost will also copy castFaceDown flag. (Fix for Illusionary Mask)

See merge request core-developers/forge!4364
2021-04-04 04:26:17 +00:00
Michael Kamensky
f70e22fa4d Merge branch 'biblio' into 'master'
STX - The Biblioplex

See merge request core-developers/forge!4361
2021-04-04 04:25:56 +00:00
Michael Kamensky
cbaa7028d0 Merge branch 'stx_3' into 'master'
STX - 3 April

See merge request core-developers/forge!4360
2021-04-04 04:25:49 +00:00
Michael Kamensky
d6a4e33035 Merge branch 'new-cards' into 'master'
STX Cards

See merge request core-developers/forge!4353
2021-04-04 04:25:14 +00:00
Adam Pantel
99bae93e07 STX Cards 2021-04-03 23:37:21 -04:00
Lyu Zong-Hong
9097fb6f60 Copy SA without mana cost will also copy castFaceDown flag. (Fix for Illusionary Mask) 2021-04-04 10:43:55 +09:00
John
8e9b91eeb6 Add new file 2021-04-03 23:22:06 +00:00
John
419b46c7cc Add new file 2021-04-03 23:21:14 +00:00
John
e927d76cb5 Add new file 2021-04-03 23:20:19 +00:00
John
966d190b77 Add new file 2021-04-03 23:19:37 +00:00
John
b7b06413d6 Add new file 2021-04-03 23:18:54 +00:00
John
67ba5e21f7 Add new file 2021-04-03 23:17:51 +00:00
John
27e401d3a1 Add new file 2021-04-03 23:17:04 +00:00
John
45c1c40e9f Add new file 2021-04-03 23:16:21 +00:00
John
29cfde5249 Upload New File 2021-04-03 23:00:13 +00:00
John
ffecb391b3 Upload New File 2021-04-03 22:59:44 +00:00
John
c87f8b0986 Add new file 2021-04-03 22:35:13 +00:00
John
8cc4eef916 Add new file 2021-04-03 22:34:05 +00:00
John
46f241f004 Add new file 2021-04-03 22:33:18 +00:00
John
4dbe0bdbcb Add new file 2021-04-03 22:32:09 +00:00
John
ee5e0beb7f Add new file 2021-04-03 22:30:56 +00:00
John
ef89a35de6 Add new file 2021-04-03 22:30:14 +00:00
Adam Pantel
26f94ccf07 Dragon's Approach 2021-04-03 18:29:57 -04:00
John
c7a0b9d31c Add new file 2021-04-03 22:29:13 +00:00
John
e2f91339b4 Add new file 2021-04-03 22:28:23 +00:00
John
155b2723f1 Add new file 2021-04-03 22:27:34 +00:00
John
2b5d5c5c12 Add new file 2021-04-03 22:26:22 +00:00
John
660f854546 Add new file 2021-04-03 22:25:42 +00:00
John
505ae78d14 Add new file 2021-04-03 22:25:02 +00:00
John
0f19934021 Add new file 2021-04-03 22:23:29 +00:00
John
0865e3262f Add new file 2021-04-03 22:22:46 +00:00
John
8d266dbc4c Add new file 2021-04-03 22:22:00 +00:00
John
8801365896 Add new file 2021-04-03 22:21:11 +00:00
John
33249ea526 Add new file 2021-04-03 22:20:26 +00:00
John
d1f3ec25fc Add new file 2021-04-03 22:18:08 +00:00
Northmoc
8ea76ee1b8 pestilent_cauldron_restorative_burst.txt 2021-04-03 15:28:29 -04:00
Michael Kamensky
660ed4c341 Merge branch 'Williams-master-patch-56008' into 'master'
STX Contibutions

See merge request core-developers/forge!4357
2021-04-03 19:00:05 +00:00
Northmoc
ef6f88a129 dramatic_finale.txt 2021-04-03 14:45:46 -04:00
Northmoc
bf072e0802 move TriggerZone param 2021-04-03 14:30:29 -04:00
John
d910521a0b Update zimone_quandrix_prodigy.txt 2021-04-03 18:22:43 +00:00
John
3ceb40741c Update zimone_quandrix_prodigy.txt 2021-04-03 18:22:15 +00:00
John
7d3a192435 Update witherbloom_pledgemage.txt 2021-04-03 18:21:39 +00:00
John
50af4b83c5 Update witherbloom_pledgemage.txt 2021-04-03 18:20:43 +00:00
John
2e4f79f6ef Update twinscroll_shaman.txt 2021-04-03 18:20:17 +00:00
John
7c57be2181 Update test_of_talents.txt 2021-04-03 18:19:52 +00:00
John
2f238789ad Update professors_warning.txt 2021-04-03 18:19:17 +00:00
John
29d4cfe669 Update professor_of_zoomancy.txt 2021-04-03 18:18:51 +00:00
John
df4eaa28b9 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-03 18:18:23 +00:00
John
42dc55c664 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-03 18:16:37 +00:00
John
a82ad37e78 Update mila_crafty_companion_lukka_wayward_bonder.txt 2021-04-03 18:15:45 +00:00
John
5fdc7970cd Update draconic_intervention.txt 2021-04-03 18:15:12 +00:00
John
8f45cc62d2 Update draconic_intervention.txt 2021-04-03 18:14:36 +00:00
Northmoc
b8eaa369c2 add AI hint 2021-04-03 13:57:34 -04:00
Northmoc
fec2a4fcd5 blot_out_the_sky.txt (Suthro) 2021-04-03 13:57:33 -04:00
Northmoc
f40ca2037b rushed_rebirth.txt (Suthro) 2021-04-03 13:57:33 -04:00
Northmoc
7227885e0e silverquill_silencer.txt (Suthro) 2021-04-03 13:57:32 -04:00
Northmoc
4fe66ddca8 add NonLegendary param to copySpellHost (for Double Major) 2021-04-03 13:57:32 -04:00
Northmoc
5472f17c96 double_major.txt 2021-04-03 13:57:32 -04:00
Northmoc
69b072f130 augmenter_pugilist_echoing_equation.txt 2021-04-03 13:57:31 -04:00
Northmoc
640106a78a jadzi_oracle_of_arcavios_journey_to_the_oracle.txt 2021-04-03 13:57:31 -04:00
Northmoc
2e31e1840a improve awkward prompt 2021-04-03 13:11:45 -04:00
Northmoc
09334d2043 support for OrActivationCardsInHand param 2021-04-03 13:10:09 -04:00
Northmoc
b52d22fd76 the_biblioplex.txt 2021-04-03 13:07:38 -04:00
Michael Kamensky
34233bf682 Merge branch 'stx_2' into 'master'
STX - 2 April

See merge request core-developers/forge!4354
2021-04-03 13:53:57 +00:00
John
3ca111f6e0 Update dina_soul_steeper.txt 2021-04-03 13:16:06 +00:00
John
09556cc3ac Update daemogoth_titan.txt 2021-04-03 13:15:38 +00:00
John
ff94d85e53 Update bookwurm.txt 2021-04-03 13:14:52 +00:00
John
dd759d62e9 Update beledros_witherbloom.txt 2021-04-03 13:13:37 +00:00
Northmoc
9739ed6b87 fix Oracle typo 2021-04-03 08:08:01 -04:00
John
3d4c88ae7c Update tempted_by_the_oriq.txt 2021-04-03 11:03:21 +00:00
John
eb5f842e7b Update zimone_quandrix_prodigy.txt 2021-04-03 10:56:22 +00:00
Anthony Calosa
6fb409dcd0 Merge branch 'kevlahnota-master-patch-68671' into 'master'
Update MagicStack.java

See merge request core-developers/forge!4359
2021-04-03 10:35:04 +00:00
Anthony Calosa
b2e36e8ca2 Update MagicStack.java
Should fix conspire (Wort, the Raidmother), replicate (Djiin Illuminatus)
2021-04-03 10:27:14 +00:00
Anthony Calosa
709524b7c9 Merge branch 'master' into 'master'
Fix spell shop items

See merge request core-developers/forge!4358
2021-04-03 09:32:39 +00:00
Anthony Calosa
4444438670 Merge remote-tracking branch 'core/master' 2021-04-03 17:29:53 +08:00
Anthony Calosa
87ada19569 fix spell shop items 2021-04-03 17:28:58 +08:00
John
082fce33c3 Update witherbloom_pledgemage.txt 2021-04-03 08:01:52 +00:00
John
ae93d2028a Update sedgemoor_witch.txt 2021-04-03 08:00:06 +00:00
John
a36719686b Add new file 2021-04-03 07:52:11 +00:00
John
981dc381cf Add new file 2021-04-03 07:51:26 +00:00
John
261241c1ee Add new file 2021-04-03 07:50:49 +00:00
John
7c8d432aa9 Add new file 2021-04-03 07:49:59 +00:00
John
bd82d5d355 Add new file 2021-04-03 07:49:17 +00:00
John
fed9270211 Add new file 2021-04-03 07:48:28 +00:00
John
2fa0ef2572 Add new file 2021-04-03 07:47:42 +00:00
John
02ffbb2b5c Add new file 2021-04-03 07:46:56 +00:00
John
c00197d44a Add new file 2021-04-03 07:46:13 +00:00
John
aa30390674 Add new file 2021-04-03 07:30:16 +00:00
John
c54bf6f730 Add new file 2021-04-03 07:27:42 +00:00
John
a5cc4cc01d Add new file 2021-04-03 07:26:58 +00:00
John
ca02c342d7 Add new file 2021-04-03 07:25:32 +00:00
John
bd58d81bcc Add new file 2021-04-03 07:24:55 +00:00
John
15dfdb5bc1 Add new file 2021-04-03 07:24:12 +00:00
John
36730dd6e2 Add new file 2021-04-03 07:23:26 +00:00
John
0122589138 Add new file 2021-04-03 07:22:41 +00:00
John
e82b1c4524 Add new file 2021-04-03 07:21:40 +00:00
John
8a0ae86b28 Add new file 2021-04-03 07:20:17 +00:00
John
d406277247 Add new file 2021-04-03 07:19:33 +00:00
John
5e5015c552 Add new file 2021-04-03 07:18:49 +00:00
John
22c46a50f0 Add new file 2021-04-03 07:17:13 +00:00
Michael Kamensky
7c9404864a Merge branch 'issue1789' into 'master'
goblin_welder.txt - improve StackDesc

Closes #1789

See merge request core-developers/forge!4355
2021-04-03 04:46:20 +00:00
Michael Kamensky
82752f55d1 Merge branch 'card-fixes' into 'master'
Fix AltCost NPE

See merge request core-developers/forge!4356
2021-04-03 04:44:36 +00:00
Adam Pantel
50a7643610 Fix AltCost NPE 2021-04-02 21:37:38 -04:00
Northmoc
a85c84096f necrotic_fumes.txt remove extra spaces 2021-04-02 20:33:43 -04:00
Northmoc
7edda12548 selfless_glyphweaver_deadly_vanity.txt 2021-04-02 20:32:47 -04:00
Northmoc
d91f1df9d8 blex_vexing_pest_search_for_blex.txt 2021-04-02 20:05:47 -04:00
Northmoc
7c2f9e529c goblin_welder.txt - improve StackDesc 2021-04-02 19:13:03 -04:00
Northmoc
d7ca06c599 CostAdjustment.java support UnlessValidTarget param for Mavinda 2021-04-02 18:41:42 -04:00
Northmoc
f4ee0441ea mavinda_students_advocate.txt 2021-04-02 18:40:21 -04:00
Michael Kamensky
7b28ea1fa3 Merge branch 'Williams-master-patch-83252' into 'master'
STX More Cards 02/04

See merge request core-developers/forge!4345
2021-04-02 19:10:35 +00:00
John
e02da44288 Add new file 2021-04-02 17:58:40 +00:00
John
e836880a39 Add new file 2021-04-02 17:46:34 +00:00
Anthony Calosa
2ef00808b7 Merge branch 'kevlahnota-master-patch-77063' into 'master'
update, add alias to SLD

See merge request core-developers/forge!4352
2021-04-02 16:36:09 +00:00
Anthony Calosa
1dca52e8cd update, add alias to SLD
Should fix some decks that uses "PSLD" set code before (ie. An unsupported card was requested: "Teferi, Time Raveler" from "PSLD". We're sorry, but you cannot use this card yet.)
2021-04-02 16:34:53 +00:00
John
a4f7bd4d2b Add new file 2021-04-02 16:02:33 +00:00
John
5740917a11 Update promising_duskmage.txt 2021-04-02 14:19:26 +00:00
John
0790220a44 Update novice_dissector.txt 2021-04-02 14:18:57 +00:00
John
462459c6c2 Update karok_wrangler.txt 2021-04-02 14:18:29 +00:00
Michael Kamensky
eee160c379 Merge branch 'gorm' into 'master'
Gorm the Great and support

See merge request core-developers/forge!4349
2021-04-02 14:09:30 +00:00
Michael Kamensky
96c1ce5803 Merge branch 'stackdesc' into 'master'
Use StackDescription for AltCosts

See merge request core-developers/forge!4344
2021-04-02 14:09:03 +00:00
Michael Kamensky
6dc705450b Merge branch 'master' into 'master'
Better fix for CloneAi

See merge request core-developers/forge!4351
2021-04-02 14:08:45 +00:00
Michael Kamensky
e9f5039817 - Better fix for CloneAi 2021-04-02 17:07:07 +03:00
Michael Kamensky
4a7d0222a2 Merge branch 'master' into 'master'
Fix AI logic for ETB CloneAi with Choices

See merge request core-developers/forge!4350
2021-04-02 14:02:43 +00:00
Michael Kamensky
63e7803ea4 - Fix AI logic for ETB CloneAi with Choices 2021-04-02 17:01:59 +03:00
John
c522710f7f Add new file 2021-04-02 12:35:47 +00:00
John
d9be2b2437 Add new file 2021-04-02 12:30:26 +00:00
John
caeaefcc6a Add new file 2021-04-02 12:10:33 +00:00
John
6048161924 Add new file 2021-04-02 12:03:44 +00:00
John
4348bc20eb Add new file 2021-04-02 11:55:49 +00:00
tool4EvEr
bff6d43701 Gorm the Great and support 2021-04-02 13:55:36 +02:00
John
944fc70ff4 Add new file 2021-04-02 11:50:49 +00:00
John
a75a148278 Add new file 2021-04-02 10:35:42 +00:00
John
a378eefb7b Add new file 2021-04-02 10:28:31 +00:00
Bug Hunter
9782705e06 Merge branch 'fixed' into 'master'
Fix typos

See merge request core-developers/forge!4348
2021-04-02 10:26:55 +00:00
tool4EvEr
4cd47da3f4 Fix typos 2021-04-02 12:25:54 +02:00
Bug Hunter
65776d8401 Merge branch 'Williams-master-patch-28392' into 'master'
Fix Arrogant Poet

See merge request core-developers/forge!4347
2021-04-02 10:25:45 +00:00
John
1c5c5b3429 Update arrogant_poet.txt 2021-04-02 10:23:33 +00:00
John
284d51918f Update tenured_inkcaster.txt 2021-04-02 10:22:31 +00:00
Anthony Calosa
06a1a49a73 Merge branch 'kevlahnota-master-patch-25321' into 'master'
Update tenured_inkcaster.txt

See merge request core-developers/forge!4346
2021-04-02 10:20:49 +00:00
Anthony Calosa
1c2c72ca7c Update tenured_inkcaster.txt 2021-04-02 10:20:37 +00:00
John
f2a28a34fe Update tome_shredder.txt 2021-04-02 09:33:14 +00:00
John
34e6eedf23 Add new file 2021-04-02 09:12:44 +00:00
John
c1042d15b5 Add new file 2021-04-02 09:04:14 +00:00
John
c40f258725 Add new file 2021-04-02 08:57:31 +00:00
John
8619c180de Add new file 2021-04-02 08:49:24 +00:00
John
1e9e5c5956 Add new file 2021-04-02 08:43:21 +00:00
tool4EvEr
095769b7cd Use StackDescription for AltCosts 2021-04-02 10:20:22 +02:00
Anthony Calosa
0740115724 Merge branch 'master' into 'master'
Remove Unused Import

See merge request core-developers/forge!4343
2021-04-02 07:52:46 +00:00
Anthony Calosa
862f2080ea Remove Unused Import 2021-04-02 15:51:46 +08:00
Anthony Calosa
2ed264b2b1 Merge branch 'new-cards-2' into 'master'
Add SpellCastOrCopy trigger

See merge request core-developers/forge!4338
2021-04-02 07:44:56 +00:00
Anthony Calosa
e9c2ea0e87 Merge branch 'master' into 'master'
Skip loading filtered card

See merge request core-developers/forge!4342
2021-04-02 06:34:41 +00:00
Anthony Calosa
79cea28630 Skip loading filtered card 2021-04-02 14:31:21 +08:00
Adam Pantel
ec6ed8cea5 Remove redundant checks 2021-04-02 00:31:49 -04:00
Michael Kamensky
eab7a03c77 Merge branch 'Williams-master-patch-02407' into 'master'
STX Vanishing Verse

See merge request core-developers/forge!4336
2021-04-02 04:08:48 +00:00
Michael Kamensky
970d82e48e Merge branch 'fuse' into 'master'
Fuse: add keyword to card text

See merge request core-developers/forge!4337
2021-04-02 04:08:27 +00:00
Michael Kamensky
a9ad5169f2 Merge branch 'squirrel' into 'master'
form_of_the_squirrel.txt fixes

See merge request core-developers/forge!4340
2021-04-02 04:08:12 +00:00
Michael Kamensky
27e0b3e62f Merge branch 'stx_1' into 'master'
STX - 1 Apr (no foolin)

See merge request core-developers/forge!4335
2021-04-02 04:07:30 +00:00
Michael Kamensky
3dfa2cd397 Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4341
2021-04-02 04:06:11 +00:00
Michael Kamensky
782eca7b95 Merge branch 'new-cards' into 'master'
STX cards

See merge request core-developers/forge!4334
2021-04-02 04:06:00 +00:00
CCTV-1
17c6e46e8d update simplified chinese translation 2021-04-02 11:40:30 +08:00
Northmoc
684b3d2b2b form_of_the_squirrel.txt fixes 2021-04-01 22:25:05 -04:00
Northmoc
e0c4ee102c mastery description updates 2021-04-01 22:17:13 -04:00
Northmoc
0766a4b76d devastating_mastery.txt 2021-04-01 22:16:47 -04:00
Northmoc
e16bd40fd3 verdant_mastery.txt 2021-04-01 22:16:31 -04:00
Hans Mackowiak
479f72f160 AbilityUtils: add Valid to xCount to fix Mana Echoes 2021-04-02 00:53:46 +02:00
John
56bfb32ad4 Add new file 2021-04-01 21:13:20 +00:00
Adam Pantel
45dbb08993 STX cards 2021-04-01 17:09:43 -04:00
Bug Hunter
5030f6e901 Merge branch 'typo' into 'master'
Fix description

See merge request core-developers/forge!4339
2021-04-01 20:44:12 +00:00
tool4EvEr
e64003a22f Fix description 2021-04-01 22:43:54 +02:00
Adam Pantel
3abdeee850 Combine into one class 2021-04-01 16:23:33 -04:00
Adam Pantel
dfec2dc308 Add SpellCastOrCopy trigger 2021-04-01 15:36:51 -04:00
John
abede2f979 Add new file 2021-04-01 19:21:50 +00:00
John
695a0dac40 Add new file 2021-04-01 19:13:14 +00:00
John
266e2e13cf Add new file 2021-04-01 19:07:41 +00:00
Northmoc
79a81c86bb add AI hint 2021-04-01 14:47:29 -04:00
tool4EvEr
20f7bf5ebf Fuse: add keyword to card text 2021-04-01 20:46:31 +02:00
Northmoc
bc3cd33a30 fervent_mastery.txt 2021-04-01 14:41:20 -04:00
John
5eb3902854 Add new file 2021-04-01 18:27:25 +00:00
Northmoc
eb75911d50 baleful_mastery.txt 2021-04-01 12:26:48 -04:00
Michael Kamensky
81b352c888 Merge branch 'ai-multiple-choice' into 'master'
Add basic Multiple Choice AI, tweak Brain in a Jar AI. Also fix Multiple Choice being uncastable.

See merge request core-developers/forge!4330
2021-04-01 15:47:46 +00:00
Michael Kamensky
4b64dff6dd Merge branch 'fixdesc' into 'master'
Some fixes

See merge request core-developers/forge!4333
2021-04-01 15:47:29 +00:00
Michael Kamensky
ea2eb39975 Merge branch 'master' into 'master'
Strixhaven fun with these new cards

See merge request core-developers/forge!4331
2021-04-01 15:46:20 +00:00
Michael Kamensky
c3d64a7d2a Merge branch 'Williams-master-patch-81587' into 'master'
STX Some more cards

See merge request core-developers/forge!4332
2021-04-01 15:46:09 +00:00
Northmoc
4049a66c45 tamiyo_collector_of_tales.txt add ValidCards to +1 2021-04-01 11:33:45 -04:00
John
b06749aff9 Add new file 2021-04-01 13:29:39 +00:00
John
6fa418b43b Add new file 2021-04-01 12:30:26 +00:00
John
831786f55e Add new file 2021-04-01 12:23:41 +00:00
Michael Kamensky
952a9b69e1 Merge branch 'Williams-master-patch-62846' into 'master'
Some STX Cards 01/04

See merge request core-developers/forge!4329
2021-04-01 11:22:59 +00:00
Michael Kamensky
8d367a1167 - Add basic Multiple Choice AI
- Fix Brain in a Jar AI, move it to SpecialCardAi
2021-04-01 14:06:53 +03:00
John
6cb366a978 Add new file 2021-04-01 10:55:51 +00:00
John
aa99373d2d Add new file 2021-04-01 10:47:01 +00:00
John
a6633365ec Add new file 2021-04-01 10:46:11 +00:00
John
cac0f46d1c Add new file 2021-04-01 10:44:47 +00:00
Hythonia
3e3b14779e STX: Golden Ratio, Strixhaven Stadium and more 2021-04-01 11:26:34 +02:00
Michael Kamensky
225767acab Merge branch 'stx_31' into 'master'
STX - 31 Mar

See merge request core-developers/forge!4326
2021-04-01 08:06:10 +00:00
Michael Kamensky
7d1f31247e Merge branch 'stxed' into 'master'
New/updated edition files

See merge request core-developers/forge!4325
2021-04-01 08:04:08 +00:00
Michael Kamensky
5fe1de5f7f Merge branch 'multiple' into 'master'
Multiple Choice

See merge request core-developers/forge!4320
2021-04-01 08:02:39 +00:00
Michael Kamensky
0ac4f98ffa Merge branch 'practical' into 'master'
Practical Research and support

See merge request core-developers/forge!4323
2021-04-01 08:02:02 +00:00
Anthony Calosa
ebd3c72170 Merge branch 'master' into 'master'
fix check

See merge request core-developers/forge!4328
2021-04-01 07:54:41 +00:00
Anthony Calosa
839a91d144 fix check 2021-04-01 15:53:10 +08:00
Anthony Calosa
5c3df787bb Merge branch 'kevlahnota-master-patch-83054' into 'master'
Update blood_of_the_martyr.txt

See merge request core-developers/forge!4327
2021-04-01 06:30:26 +00:00
Anthony Calosa
daeec63b71 Update blood_of_the_martyr.txt 2021-04-01 06:30:09 +00:00
Northmoc
1aea0aa24c archway_commons.txt 2021-03-31 22:08:50 -04:00
Northmoc
7b53957e55 kasmina AI tweak 2021-03-31 22:04:08 -04:00
Northmoc
09c7af4cc4 manifestation_sage.txt 2021-03-31 22:03:49 -04:00
Northmoc
7ed9526828 leonin_lightscribe.txt 2021-03-31 21:53:09 -04:00
Northmoc
27c61f1596 hall_of_oracles.txt 2021-03-31 21:47:45 -04:00
Northmoc
ca8e4d19bf quandrix_campus.txt 2021-03-31 21:27:04 -04:00
Northmoc
938114f942 Strixhaven School of Mages.txt update 2021-03-31 17:10:58 -04:00
Northmoc
b7c61baafb Commander 2021.txt edition file 2021-03-31 17:10:08 -04:00
Northmoc
5850e271c3 moorland_haunt.txt clean up 2021-03-31 16:57:29 -04:00
Northmoc
7ab15776df ecological_appreciation.txt 2021-03-31 16:51:17 -04:00
Anthony Calosa
7f0b819993 Merge branch 'master' into 'master'
[Mobile] preload ItemPool and fix Planar Conquest new game

See merge request core-developers/forge!4324
2021-03-31 18:35:35 +00:00
Anthony Calosa
c0af1fa1eb [Mobile] preload ItemPool and fix Planar Conquest new game 2021-04-01 02:34:14 +08:00
Northmoc
316eb681f6 add AI hint 2021-03-31 10:09:28 -04:00
Northmoc
2a841e4ae2 AI multi UnlessType support 2021-03-31 09:59:54 -04:00
Northmoc
0033de0831 human multi UnlessType support 2021-03-31 09:58:54 -04:00
Northmoc
f70af84e16 practical_research.txt 2021-03-31 09:32:56 -04:00
Michael Kamensky
d9571f3c4d Merge branch 'Williams-master-patch-43202' into 'master'
STX some more cards

See merge request core-developers/forge!4317
2021-03-31 09:59:08 +00:00
Michael Kamensky
fda2f1ff15 Merge branch 'refactor_loadnonlegal' into 'master'
refactor enable non legal cards

See merge request core-developers/forge!4322
2021-03-31 09:58:54 +00:00
John
0d4824b7e0 Update sudden_breakthrough.txt 2021-03-31 07:45:18 +00:00
Hans Mackowiak
0cd376e039 CopySpellAbilityEffect: better description for optional 2021-03-31 09:26:45 +02:00
Hans Mackowiak
101c953020 Culmination of studies: better use of Remembered 2021-03-31 09:18:24 +02:00
John
46bf18cbfa Update resculpt.txt 2021-03-31 07:09:08 +00:00
Anthony Calosa
5348cd3f22 refactor enable non legal cards 2021-03-31 14:01:25 +08:00
Michael Kamensky
9591ed744b Merge branch 'stx_30' into 'master'
STX - 30 Mar

See merge request core-developers/forge!4316
2021-03-31 04:51:15 +00:00
Michael Kamensky
efed8bbd4b Merge branch 'new-cards' into 'master'
STX cards

See merge request core-developers/forge!4319
2021-03-31 04:50:11 +00:00
Michael Kamensky
7e0025d3fc Merge branch 'sym-master' into 'master'
Master Symmetrist

See merge request core-developers/forge!4308
2021-03-31 04:49:11 +00:00
Michael Kamensky
b016b56a30 Merge branch 'typo' into 'master'
typo

See merge request core-developers/forge!4321
2021-03-31 04:48:57 +00:00
Michael Kamensky
167d577f4d Merge branch 'gold' into 'master'
Fix Bound in Gold typos

See merge request core-developers/forge!4318
2021-03-31 04:48:50 +00:00
Northmoc
8888c97e07 multiple_choice.txt 2021-03-30 23:01:05 -04:00
Northmoc
b99e1f7b28 support for second SVar check 2021-03-30 22:59:59 -04:00
Adam Pantel
39b09c5b25 Symmetry Master 2021-03-30 19:31:41 -04:00
Adam Pantel
86842735bf STX cards 2021-03-30 18:37:33 -04:00
Northmoc
05cf67d3e1 prompt support for Wandering Archaic 2021-03-30 18:19:00 -04:00
Northmoc
9f10d1b923 wandering_archaic_explore_the_vastlands.txt 2021-03-30 18:19:00 -04:00
Northmoc
db66c6cde4 culmination_of_studies.txt 2021-03-30 18:19:00 -04:00
Northmoc
0eaa99a91a prismari_campus.txt 2021-03-30 18:18:59 -04:00
Northmoc
11de057064 extus_oriq_overlord_awaken_the_blood_avatar.txt and token 2021-03-30 18:18:59 -04:00
Northmoc
d206ed5c0b tanazir_quandrix.txt 2021-03-30 18:18:58 -04:00
Northmoc
6dd0275e5a galazeth_prismari.txt 2021-03-30 18:18:58 -04:00
Hans Mackowiak
cefaa78832 Semester's End: needs to be ReplacementEffect like Spark Double 2021-03-30 22:52:10 +02:00
John
7253c56086 Add new file 2021-03-30 20:27:39 +00:00
tool4EvEr
38dc4274ff Fix Bound in Gold typos 2021-03-30 21:59:30 +02:00
John
833a5bd974 Update sudden_breakthrough.txt 2021-03-30 19:43:18 +00:00
John
2443772e78 Add new file 2021-03-30 19:41:50 +00:00
John
243a24c7f2 Add new file 2021-03-30 19:34:22 +00:00
Northmoc
1166297dda fetch counter 2021-03-30 15:29:33 -04:00
Anthony Calosa
58bfc4b36c Merge branch 'tweaks' into 'master'
Tweaks

See merge request core-developers/forge!4315
2021-03-30 17:53:20 +00:00
Anthony Calosa
1b07c8838f Merge branch 'kevlahnota-master-patch-57147' into 'master'
fix NPE

See merge request core-developers/forge!4314
2021-03-30 17:28:31 +00:00
Anthony Calosa
6592694778 fix NPE 2021-03-30 17:28:08 +00:00
Northmoc
08d5735af9 one SubAbility per line 2021-03-30 13:22:07 -04:00
Northmoc
4cd69a52f6 better prompt 2021-03-30 13:21:13 -04:00
Michael Kamensky
542bab816f Merge branch 'new-cards' into 'master'
STX cards

See merge request core-developers/forge!4306
2021-03-30 17:19:24 +00:00
Michael Kamensky
357be5296f Merge branch 'Williams-master-patch-83839' into 'master'
STX some cards 30/03

See merge request core-developers/forge!4310
2021-03-30 17:18:19 +00:00
Hans Mackowiak
57816a4f3a fixup DestroyAi use getMaxXValue 2021-03-30 19:08:15 +02:00
John
7486eb640f Add new file 2021-03-30 16:51:31 +00:00
John
e946e23360 Update illustrious_historian.txt 2021-03-30 16:47:00 +00:00
Adam Pantel
e233eed8eb STX cards 2021-03-30 12:44:14 -04:00
Michael Kamensky
80ac8906b3 Merge branch 'stx_29' into 'master'
STX - 29 Mar

See merge request core-developers/forge!4302
2021-03-30 16:24:46 +00:00
Michael Kamensky
ee9cb656fc Merge branch 'master' into 'master'
STX: A couple more cards

See merge request core-developers/forge!4305
2021-03-30 16:23:44 +00:00
Michael Kamensky
cfe9f8e6e6 Merge branch 'master' into 'master'
Fix Heliod's Intervention AI

See merge request core-developers/forge!4313
2021-03-30 16:23:24 +00:00
Michael Kamensky
72f768cca4 - Fix Heliod's Intervention AI 2021-03-30 19:22:46 +03:00
Northmoc
9d0206c432 lorehold_excavation.txt AI fix 2021-03-30 12:19:35 -04:00
Anthony Calosa
53bc91d399 Merge branch 'master' into 'master'
refactor CardDB

See merge request core-developers/forge!4312
2021-03-30 15:44:06 +00:00
Anthony Calosa
ca63763d04 refactor CardDB
use filterentries instead of declaring maps
2021-03-30 23:42:33 +08:00
John
ac7589a3ff Add new file 2021-03-30 15:03:48 +00:00
John
d752442aa3 Add new file 2021-03-30 14:48:53 +00:00
Anthony Calosa
930c27a890 Merge branch 'Williams-master-patch-81518' into 'master'
Update returned_pastcaller.txt

See merge request core-developers/forge!4311
2021-03-30 11:24:28 +00:00
John
2caec7be0e Update returned_pastcaller.txt 2021-03-30 10:44:50 +00:00
John
a7cfe98ca3 Update illustrious_historian.txt 2021-03-30 10:03:10 +00:00
John
bad5f6f431 Update illustrious_historian.txt 2021-03-30 10:02:51 +00:00
John
d0116c852c Add new file 2021-03-30 10:01:56 +00:00
Hythonia
3f3ce39b30 STX: Yet more cards 2021-03-30 10:02:08 +02:00
Bug Hunter
8a5692e9df Merge branch 'TRT-master-patch-00357' into 'master'
ActivateAbilityAi: small revert/align for multiplayer

See merge request core-developers/forge!4309
2021-03-30 07:58:56 +00:00
Bug Hunter
5cf96b1154 Update forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java 2021-03-30 07:56:46 +00:00
Michael Kamensky
2d392de9ef Merge branch 'spellAbilityAdditionalAbilities' into 'master'
SpellAbility: use SpellAbility for additionalAbilities so Trigger can have cost

See merge request core-developers/forge!4303
2021-03-30 04:47:45 +00:00
Hans Mackowiak
f53aff1882 SpellAbility: use SpellAbility for additionalAbilities so Trigger can have cost 2021-03-30 04:47:44 +00:00
Michael Kamensky
0cf6cbf3bc Merge branch 'proliferate' into 'master'
Fix AI killing itself with poison + proliferate

See merge request core-developers/forge!4307
2021-03-30 04:12:46 +00:00
Michael Kamensky
28b428a6f1 Merge branch 'Williams-master-patch-92703' into 'master'
STX more cards

See merge request core-developers/forge!4300
2021-03-30 04:12:10 +00:00
Northmoc
d311ced441 campus_guide.txt 2021-03-29 19:10:17 -04:00
tool4EvEr
3ccce18f23 Fix AI killing itself with poison + proliferate 2021-03-29 23:27:38 +02:00
Northmoc
0ecd719799 curate.txt 2021-03-29 17:12:12 -04:00
Northmoc
5438e20b01 lorehold_campus.txt 2021-03-29 15:10:42 -04:00
Northmoc
b5f72ecc5c hofri_ghostforge.txt 2021-03-29 14:06:35 -04:00
Hythonia
dd20c8caf0 STX: Some more cards 2021-03-29 19:49:27 +02:00
Northmoc
c3b96e4f68 illuminate_history.txt 2021-03-29 13:09:51 -04:00
Northmoc
b3132aa757 reconstruct_history.txt 2021-03-29 13:09:50 -04:00
Northmoc
7685e3233f blade_historian.txt 2021-03-29 13:09:50 -04:00
Northmoc
ee3e66580d lorehold_excavation.txt 2021-03-29 13:09:50 -04:00
Northmoc
26cd2acbcd velomachus_lorehold.txt 2021-03-29 13:09:49 -04:00
Northmoc
0c3b099265 radiant_scrollwielder.txt 2021-03-29 13:09:49 -04:00
Bug Hunter
6971d320ae Merge branch 'fixRemembered' into 'master'
Fix RememberDiscarded

See merge request core-developers/forge!4304
2021-03-29 17:03:48 +00:00
John
2dd6305e54 Add new file 2021-03-29 17:02:42 +00:00
tool4EvEr
baf5479d0b Fix RememberDiscarded 2021-03-29 18:52:13 +02:00
Hythonia
c57be04143 STX: Some cards 2021-03-29 18:40:17 +02:00
John
f1030c2f10 Add new file 2021-03-29 16:31:09 +00:00
John
1f1639e661 Update quintorius_field_historian.txt 2021-03-29 16:10:06 +00:00
John
04d3445834 Add new file 2021-03-29 16:05:58 +00:00
John
1559a14e39 Add new file 2021-03-29 15:58:22 +00:00
John
fbbc6aa005 Update quintorius_field_historian.txt 2021-03-29 15:31:58 +00:00
John
9c573b9bfd Update returned_pastcaller.txt 2021-03-29 15:01:17 +00:00
John
96bbe2c7dc Update returned_pastcaller.txt 2021-03-29 14:59:55 +00:00
John
ec9f6b82d9 Update quintorius_field_historian.txt 2021-03-29 14:58:26 +00:00
John
2c120c3c45 Add new file 2021-03-29 14:58:03 +00:00
John
b911f5c45b Update returned_pastcaller.txt 2021-03-29 14:56:38 +00:00
John
6366a22ff3 Update returned_pastcaller.txt 2021-03-29 14:55:17 +00:00
John
5fc819d8f5 Add new file 2021-03-29 14:43:15 +00:00
John
df9d1ddcbf Add new file 2021-03-29 14:00:44 +00:00
Anthony Calosa
eed7b282d3 Merge branch 'master' into 'master'
Refactor carddb for Deck Editor/Workshop Catalog

Closes #1436

See merge request core-developers/forge!4298
2021-03-29 13:44:55 +00:00
Anthony Calosa
fc6237cd29 update advance filter
support alternate parts limited to card names, keywords, rules text, types and subtypes
2021-03-29 21:26:14 +08:00
John
c473e634e0 Add new file 2021-03-29 12:20:38 +00:00
John
28866fc1dc Add new file 2021-03-29 12:17:00 +00:00
John
8028e8ca12 Add new file 2021-03-29 12:13:42 +00:00
Anthony Calosa
28d1cc2055 Merge remote-tracking branch 'core/master' 2021-03-29 18:30:55 +08:00
Anthony Calosa
cffd99b68d update advance filter 2021-03-29 18:30:09 +08:00
Michael Kamensky
ac92a92090 Merge branch 'targetselection' into 'master'
TargetSelection: Fix stack peeking detection

See merge request core-developers/forge!4295
2021-03-29 08:00:21 +00:00
Bug Hunter
28dbb309cf TargetSelection: Fix stack peeking detection 2021-03-29 08:00:21 +00:00
Michael Kamensky
7b585aaa05 Merge branch 'descriptions' into 'master'
Fix alternate state descriptions when viewed from original

See merge request core-developers/forge!4277
2021-03-29 07:59:43 +00:00
Bug Hunter
82f0c85bd0 Fix alternate state descriptions when viewed from original 2021-03-29 07:59:43 +00:00
Michael Kamensky
1c0a7d8475 Merge branch 'Williams-master-patch-39368' into 'master'
Update adrix_and_nev_twincasters.txt

See merge request core-developers/forge!4296
2021-03-29 07:58:48 +00:00
Michael Kamensky
5f63b5f907 Merge branch 'card-fixes' into 'master'
Inzerva doesn't target

See merge request core-developers/forge!4297
2021-03-29 07:58:38 +00:00
Anthony Calosa
7459cd65d9 unused variable 2021-03-29 15:23:57 +08:00
Anthony Calosa
0b61ddaff7 fix advance filter for adventure and flip 2021-03-29 15:23:03 +08:00
Anthony Calosa
d554a7ee97 add getUniqueCardsNoAlt for mobile 2021-03-29 13:35:20 +08:00
Anthony Calosa
3d1e130b1e Merge remote-tracking branch 'core/master' 2021-03-29 12:47:11 +08:00
Anthony Calosa
a63e008f84 Merge branch 'kevlahnota-master-patch-97416' into 'master'
fix cards

See merge request core-developers/forge!4299
2021-03-29 04:46:29 +00:00
Anthony Calosa
d76ae27611 fix cards 2021-03-29 04:46:29 +00:00
Anthony Calosa
bf00d79e23 refactor carddb for Deck Editor/Workshop Catalog 2021-03-29 11:19:26 +08:00
Adam Pantel
c08775f3e3 Inzerva doesn't target 2021-03-28 22:03:12 -04:00
John
d94ceaf492 Update adrix_and_nev_twincasters.txt 2021-03-28 21:57:32 +00:00
Michael Kamensky
9802cb62d9 Merge branch 'update_word_of_command' into 'master'
Update Word of Command mana payment check conditions

See merge request core-developers/forge!4291
2021-03-28 12:26:30 +00:00
Anthony Calosa
2c4f90b31c Merge branch 'master' into 'master'
Restrict Deck Editor cards

See merge request core-developers/forge!4294
2021-03-28 09:59:21 +00:00
Anthony Calosa
163d95800b Restrict Deck Editor cards
- fix duplicate cards
2021-03-28 17:55:23 +08:00
Michael Kamensky
0662d44a86 Merge branch 'Williams-master-patch-67517' into 'master'
Update Strixhaven Mystical Archive.txt

See merge request core-developers/forge!4293
2021-03-28 09:36:11 +00:00
Michael Kamensky
c3792191a2 Merge branch 'Williams-master-patch-03072' into 'master'
STX Lesson Cards

See merge request core-developers/forge!4289
2021-03-28 09:36:01 +00:00
Michael Kamensky
97ff18c3e2 Merge branch 'uvilda' into 'master'
Uvilda

See merge request core-developers/forge!4288
2021-03-28 09:35:44 +00:00
Michael Kamensky
d069252f76 Merge branch 'new-cards' into 'master'
Zaffai

See merge request core-developers/forge!4290
2021-03-28 09:35:39 +00:00
John
54fda151be Update Strixhaven Mystical Archive.txt 2021-03-28 08:43:02 +00:00
John
04c4cf4426 Update osgir_the_Reconstructor.txt 2021-03-28 08:04:23 +00:00
John
7c81fe3339 Add new file 2021-03-28 08:03:38 +00:00
John
330862e6c0 Update adrix_and_nev_twincasters.txt 2021-03-28 07:41:09 +00:00
John
ce309a384e Update environmental_sciences.txt 2021-03-28 07:38:14 +00:00
Adam Pantel
6bc01c3472 Uvilda 2021-03-28 03:14:37 -04:00
Adam Pantel
9ecf3e11bf Zaffai 2021-03-28 03:12:59 -04:00
Bug Hunter
7da306eb65 Merge branch 'fixDraw' into 'master'
Fix NPE when targeting different players hidden zone

See merge request core-developers/forge!4292
2021-03-28 07:08:44 +00:00
tool4EvEr
3d9d6d45fe Fix NPE when targeting different players hidden zone 2021-03-28 09:05:31 +02:00
Lyu Zong-Hong
7fc85a47be Update Word of Command mana payment check conditions 2021-03-28 15:09:01 +09:00
John
6accf7fd6c Update environmental_sciences.txt 2021-03-27 22:42:37 +00:00
John
f619155667 Update introduction_to_anihilation.txt 2021-03-27 19:34:23 +00:00
John
89d50af521 Update expanded_anatomy.txt 2021-03-27 19:32:18 +00:00
John
fe3a111d6f Update adrix_and_nev_twincasters.txt 2021-03-27 19:31:43 +00:00
John
75b6b6316b Add new file 2021-03-27 19:30:57 +00:00
John
f64822a18b Add new file 2021-03-27 19:29:45 +00:00
John
e566fb2d57 Add new file 2021-03-27 19:29:14 +00:00
John
9f5931509c Add new file 2021-03-27 19:28:39 +00:00
John
deb7ee8c3a Add new file 2021-03-27 19:27:13 +00:00
Hans Mackowiak
6a56e29474 Breena with better Choices 2021-03-27 17:19:39 +01:00
Michael Kamensky
c79ccd7e23 Merge branch 'stx_27' into 'master'
STX - 27 Mar

See merge request core-developers/forge!4287
2021-03-27 15:54:51 +00:00
Michael Kamensky
36e395e3a3 Merge branch 'master' into 'master'
STX cards + Abundant Harvest

See merge request core-developers/forge!4285
2021-03-27 15:54:25 +00:00
Northmoc
c0b106c7ef valentin_dean_of_the_vein_lisette_dean_of_the_root.txt 2021-03-27 11:40:28 -04:00
Hythonia
23c8c5da19 Will fix 2021-03-27 15:37:30 +01:00
Hythonia
883f1a7cf8 DeckHas/DeckHints 2021-03-27 15:35:37 +01:00
Michael Kamensky
b2ff43bd9b Merge branch 'twosat-master-patch-55523' into 'master'
Update de-DE.properties

See merge request core-developers/forge!4284
2021-03-27 14:06:30 +00:00
Michael Kamensky
471bb1cacd Merge branch 'fix_mutate_mdfc' into 'master'
Fix crash when mutate under an MDFC backside creature.

See merge request core-developers/forge!4286
2021-03-27 14:05:08 +00:00
Northmoc
de30329a58 prismari_apprentice.txt 2021-03-27 09:23:51 -04:00
Lyu Zong-Hong
c990c8af56 Fix crash when mutate under an MDFC backside creature. 2021-03-27 22:00:20 +09:00
Hythonia
2dbd099c68 STX: Inkling type 2021-03-27 13:55:54 +01:00
Hythonia
25da363d56 STX: Rowan // Will 2021-03-27 13:50:10 +01:00
Hythonia
a957566fc9 STX: Lorehold Apprentice 2021-03-27 13:28:45 +01:00
Michael Kamensky
97db42427e Merge branch 'shaileEnterUnder' into 'master'
Card: add turnInController to keep track under which control it entered

See merge request core-developers/forge!4282
2021-03-27 12:14:09 +00:00
Hythonia
34fabaab5f STA: Abundant Harvest 2021-03-27 12:33:41 +01:00
Andreas Bendel
11893e244e Update de-DE.properties
some Choose-strings
2021-03-27 11:29:17 +00:00
Hythonia
24f1285a43 Breena and PlayerProperty.java update 2021-03-27 12:02:13 +01:00
Anthony Calosa
2d2e0fd6c0 Merge branch 'kevlahnota-master-patch-21990' into 'master'
set correct imagekey for rollback

See merge request core-developers/forge!4283
2021-03-27 10:54:51 +00:00
Anthony Calosa
5ea2aca8a8 set correct imagekey for rollback 2021-03-27 10:51:53 +00:00
Hans Mackowiak
088b4fa07e Card: add turnInController to keep track under which control it entered 2021-03-27 11:35:52 +01:00
Hans Mackowiak
7dcfbc409c STX: fix Execute$ Execute$ 2021-03-27 10:13:06 +01:00
Michael Kamensky
44bb2ff0f5 Merge branch 'drawing' into 'master'
Move all hidden zone changing after choosing

See merge request core-developers/forge!4256
2021-03-27 04:37:27 +00:00
Bug Hunter
4effb02219 Move all hidden zone changing after choosing 2021-03-27 04:37:27 +00:00
Michael Kamensky
5d7ad6bb81 Merge branch 'master' into 'master'
STX: One Ward, two card

See merge request core-developers/forge!4273
2021-03-27 04:37:13 +00:00
Michael Kamensky
2bef5d7653 Merge branch 'c21_26' into 'master'
some C21 scripts

See merge request core-developers/forge!4274
2021-03-27 04:36:54 +00:00
Michael Kamensky
6b76bec480 Merge branch 'new-cards' into 'master'
Some STX cards

See merge request core-developers/forge!4276
2021-03-27 04:36:31 +00:00
Michael Kamensky
76ab53078e Merge branch 'stx_26' into 'master'
STX - 26 Mar (+Lesson type)

See merge request core-developers/forge!4278
2021-03-27 04:35:36 +00:00
Michael Kamensky
ddf8df9c71 Merge branch 'fixStartDraw' into 'master'
Don't count draws before game started

Closes #1781

See merge request core-developers/forge!4275
2021-03-27 04:34:09 +00:00
Michael Kamensky
b312f28ab6 Merge branch 'add_word_of_command' into 'master'
Add Word of Command

See merge request core-developers/forge!4281
2021-03-27 04:33:42 +00:00
Michael Kamensky
d9e536b789 Merge branch 'shadrix' into 'master'
STX: Shadrix, token, and CharmEffect support

See merge request core-developers/forge!4271
2021-03-27 04:33:01 +00:00
Lyu Zong-Hong
2e768cb77b Add Word of Command 2021-03-27 12:25:20 +09:00
Northmoc
d611dbd386 quandrix_apprentice.txt 2021-03-26 20:30:43 -04:00
Northmoc
8976e238a5 silverquill_apprentice.txt 2021-03-26 20:27:22 -04:00
Northmoc
eb13bb8b6d witherbloom_apprentice.txt 2021-03-26 20:26:05 -04:00
Northmoc
d72eba1602 archmage_emeritus.txt 2021-03-26 20:21:45 -04:00
Northmoc
cd4685c093 eager_first_year.txt 2021-03-26 20:08:19 -04:00
Northmoc
bc3aa56ed9 remove unneeded NoResolvingCheck 2021-03-26 19:50:13 -04:00
Northmoc
903ead9e91 star_pupil.txt 2021-03-26 19:47:33 -04:00
Northmoc
bacbd8baf3 confront_the_past.txt 2021-03-26 19:36:07 -04:00
Bug Hunter
407c742124 Merge branch 'fixNPE' into 'master'
AI: Fix NPE

See merge request core-developers/forge!4280
2021-03-26 21:34:09 +00:00
tool4EvEr
34a91b7e3d Fix NPE 2021-03-26 22:33:23 +01:00
Northmoc
65f4724f87 add Lesson type 2021-03-26 16:56:21 -04:00
Northmoc
afafe46221 pest_summoning.txt and token 2021-03-26 16:55:56 -04:00
Northmoc
ff220113af storm_kiln_artist.txt (script by ?) 2021-03-26 15:28:00 -04:00
Adam Pantel
81c2c482cd Treasure Keeper, Kianne, Plargg 2021-03-26 15:25:10 -04:00
tool4EvEr
afb06baa56 Don't count draws before game started 2021-03-26 20:20:24 +01:00
Northmoc
d25ff61f22 dragonsguard_elite.txt (script by ?) 2021-03-26 15:12:08 -04:00
Northmoc
067f0b3a66 bandaid for CharmEffect.java description builder 2021-03-26 14:44:32 -04:00
Northmoc
0f71efd1e9 willowdusk_essence_seer.txt 2021-03-26 14:39:01 -04:00
Hythonia
351f9c1976 STX: Ward's TriggerDescription 2021-03-26 18:14:27 +01:00
Northmoc
a11eff4d9a add Optional param and prompt to CharmEffect.java 2021-03-26 12:44:11 -04:00
Northmoc
fe6a5a700a add optional Charm prompt 2021-03-26 12:43:34 -04:00
Northmoc
87a994b8ce shadrix_silverquill.txt v2 2021-03-26 12:42:52 -04:00
Hythonia
3f06553604 Merge remote-tracking branch 'origin/master' 2021-03-26 16:34:34 +01:00
Northmoc
e9f765a216 wb_2_1_inkling_flying.txt 2021-03-26 11:29:28 -04:00
Northmoc
532e65f3a0 shadrix_silverquill.txt 2021-03-26 11:29:08 -04:00
Hythonia
b038044347 STX: One Ward, two card 2021-03-26 15:01:59 +01:00
Michael Kamensky
0664d11f55 Merge branch 'master' into 'master'
Basic logic for Professor Onyx

See merge request core-developers/forge!4270
2021-03-26 11:48:04 +00:00
Michael Kamensky
6d00a26530 - Basic logic for Professor Onyx. 2021-03-26 14:38:10 +03:00
Michael Kamensky
a4d28b13ef Merge branch 'AIopponent' into 'master'
AI Multiplayer improvements

See merge request core-developers/forge!4239
2021-03-26 10:58:22 +00:00
Bug Hunter
fd01dc1bb1 AI Multiplayer improvements 2021-03-26 10:58:22 +00:00
Michael Kamensky
fa72d3d0de Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!4269
2021-03-26 10:57:20 +00:00
Michael Kamensky
f935f57d23 Merge branch 'add_camouflage' into 'master'
Add Camouflage

See merge request core-developers/forge!4268
2021-03-26 10:57:03 +00:00
Michael Kamensky
fbf1e855ad Merge branch 'snarls' into 'master'
STX rare land cycle

See merge request core-developers/forge!4263
2021-03-26 10:56:25 +00:00
Michael Kamensky
50a6037a6d Merge branch 'onyx' into 'master'
professor_onyx.txt

See merge request core-developers/forge!4262
2021-03-26 10:56:10 +00:00
CCTV-1
d15d27defb update simplified chinese translation 2021-03-26 18:40:04 +08:00
Hythonia
7952bdcb48 Revert "ManaEffect rework"
This reverts commit 9db11ce6
2021-03-26 11:37:57 +01:00
Hythonia
dfdf3160d3 Merge remote-tracking branch 'origin/master' 2021-03-26 11:20:36 +01:00
Lyu Zong-Hong
ce4f12179c Add Camouflage 2021-03-26 13:05:04 +09:00
Leandro Doctors
491da787b4 Merge branch 'update-building-docs' into 'master'
doc: update Android building instructions

See merge request core-developers/forge!4265
2021-03-26 02:01:43 +00:00
Leandro Doctors
6dbc7e2704 doc: update Android building instructions 2021-03-25 22:58:15 -03:00
Leandro Doctors
2a39c917ee Merge branch 'mention-hardcoded-value' into 'master'
add FIXME for hardcoded value

See merge request core-developers/forge!4264
2021-03-26 01:51:37 +00:00
Leandro Doctors
3733ba45e2 add FIXME for hardcoded value 2021-03-25 22:50:13 -03:00
Northmoc
2a410b9099 snarls 2021-03-25 20:14:22 -04:00
Northmoc
3ccc7c41e7 professor_onyx.txt v2 2021-03-25 18:13:33 -04:00
Northmoc
d03bc797fc DiscardEffect.java add "RememberDiscardingPlayers" param 2021-03-25 18:13:10 -04:00
Leandro Doctors
5c4f8eb526 Merge branch 'clean-up-pom' into 'master'
Delete obsolete 'developers' tags (pom.xml)

See merge request core-developers/forge!4219
2021-03-25 17:41:42 +00:00
Northmoc
973464f1c4 professor_onyx.txt 2021-03-25 10:56:40 -04:00
Hans Mackowiak
e090ab2825 Merge branch '1779-lingering-hostage-taker-untilhostleavesplay-issue' into 'master'
Resolve "Lingering Hostage Taker (UntilHostLeavesPlay) issue"

Closes #1779

See merge request core-developers/forge!4261
2021-03-25 13:51:07 +00:00
Hans Mackowiak
0413e5ded8 Fix ExiledWith being cleared when successfully added to MagicStack 2021-03-25 14:50:25 +01:00
Michael Kamensky
51c24f6fdf Merge branch 'fix_invasion_plans' into 'master'
Fix Invasion Plans

See merge request core-developers/forge!4260
2021-03-25 13:40:21 +00:00
Lyu Zong-Hong
d1de248894 Fix Invasion Plans 2021-03-25 21:19:28 +09:00
Hans Mackowiak
fdf3e618c2 Merge branch '1778-untilhostleavesplay-missing-a-check-on-hostage-taker' into 'master'
Resolve "UntilHostLeavesPlay missing a check on Hostage Taker"

Closes #1778

See merge request core-developers/forge!4259
2021-03-25 07:52:04 +00:00
Hans Mackowiak
5b0a228f4f SpellAbilityEffect: fix untilHostLeavesPlayCommand to check the until CardCollection 2021-03-25 08:51:35 +01:00
Hythonia
9db11ce690 ManaEffect rework 2021-03-24 20:14:41 +01:00
Leandro Doctors
f9bdf6e181 Delete obsolete 'developers' tags
That information is obsolete. The up to date version can always be obtained form version control.
2021-03-19 11:38:30 -03:00
2669 changed files with 25134 additions and 10360 deletions

View File

@@ -1,15 +1,15 @@
# Forge # Forge
Gitlab repo is found [here](https://git.cardforge.org/core-developers/forge). [Official GitLab repo](https://git.cardforge.org/core-developers/forge).
Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated) Dev instructions here: [Getting Started](https://www.slightlymagic.net/wiki/Forge:How_to_Get_Started_Developing_Forge) (Somewhat outdated)
Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968) Discord channel [here](https://discordapp.com/channels/267367946135928833/267742313390931968)
# Requirements / Tools ## Requirements / Tools
- Java IDE such as IntelliJ or Eclipse - you favourite Java IDE (IntelliJ, Eclipse, VSCodium, Emacs, Vi...)
- Java JDK 8 or later - Java JDK 8 or later (some IDEs such as Eclipse require JDK11+, whereas the Android build currently only works with JDK8)
- Git - Git
- Git client (optional) - Git client (optional)
- Maven - Maven
@@ -18,7 +18,7 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
- Android SDK (optional: for Android releases) - Android SDK (optional: for Android releases)
- RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx) - RoboVM (optional: for iOS releases) (TBD: Current status of support by libgdx)
# Project Quick Setup ## Project Quick Setup
- Log in to gitlab with your user account and fork the project. - Log in to gitlab with your user account and fork the project.
@@ -26,11 +26,11 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
- Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install` - Go to the project location on your machine. Run Maven to download all dependencies and build a snapshot. Example for Windows & Linux: `mvn -U -B clean -P windows-linux install`
# Eclipse ## Eclipse
Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary. Eclipse includes Maven integration so a separate install is not necessary. For other IDEs, your mileage may vary.
## Project Setup ### Project Setup
- Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined. - Follow the instructions for cloning from Gitlab. You'll need a Gitlab account setup and an SSH key defined.
@@ -55,9 +55,9 @@ Eclipse includes Maven integration so a separate install is not necessary. For
- Once everything builds, all errors should disappear. You can now advance to Project launch. - Once everything builds, all errors should disappear. You can now advance to Project launch.
## Project Launch ### Project Launch
### Desktop #### Desktop
This is the standard configuration used for releasing to Windows / Linux / MacOS. This is the standard configuration used for releasing to Windows / Linux / MacOS.
@@ -65,7 +65,7 @@ This is the standard configuration used for releasing to Windows / Linux / MacOS
- The familiar Forge splash screen, etc. should appear. Enjoy! - The familiar Forge splash screen, etc. should appear. Enjoy!
### Mobile (Desktop dev) #### Mobile (Desktop dev)
This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here. This is the configuration used for doing mobile development using the Windows / Linux / MacOS front-end. Knowledge of libgdx is helpful here.
@@ -73,24 +73,24 @@ This is the configuration used for doing mobile development using the Windows /
- A view similar to a mobile phone should appear. Enjoy! - A view similar to a mobile phone should appear. Enjoy!
## Eclipse / Android SDK Integration ### Eclipse / Android SDK Integration
Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms. Google no longer supports Android SDK releases for Eclipse. That said, it is still possible to build and debug Android platforms.
### Android SDK #### Android SDK
Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk Reference SO for obtaining a specific release: https://stackoverflow.com/questions/27043522/where-can-i-download-an-older-version-of-the-android-sdk
#### Windows ##### Windows
Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced Download the following archived version of the Android SDK: http://dl-ssl.google.com/android/repository/tools_r25.2.3-windows.zip. Install it somewhere on your machine. This is referenced
in the following instructions as your 'Android SDK Install' path. in the following instructions as your 'Android SDK Install' path.
#### Linux / Mac OSX ##### Linux / Mac OSX
TBD TBD
### Android Plugin for Eclipse #### Android Plugin for Eclipse
Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin Google's last plugin release does not work completely with target's running Android 7.0 or later. Download the ADT-24.2.0-20160729.zip plugin
from: https://github.com/khaledev/ADT/releases from: https://github.com/khaledev/ADT/releases
@@ -98,25 +98,24 @@ from: https://github.com/khaledev/ADT/releases
In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse In Eclipse go to: Help > Install New Software... > Add > Name: ADT Update, Click on the "Archive:" button and navigate to the downloaded ADT-24.2.0-20160729.zip file > Add. Install all "Developer Tools". Eclipse
should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below. should restart and prompt you to run the SDK Manager. Launch it and continue to the next steps below.
### Android Platform #### Android Platform
In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions: In Eclipse, if the SDK Manager is not already running, go to Window > Android SDK Manager. Install the following options / versions:
- Android SDK Build-tools 26.0.1 - Android SDK Build-tools 26.0.1
- Android 7.1.1 (API 25) SDK Platform - Android 8.0.0 (API 26) SDK Platform
- Google USB Driver 11 - Google USB Driver (in case your phone is not detected by ADB)
Note that this will populate additional tools in the Android SDK install path extracted above. Note that this will populate additional tools in the Android SDK install path extracted above.
### Proguard update #### Proguard update
The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 from https://sourceforge.net/projects/proguard/files/proguard/6.0/. The Proguard included with the Android SDK Build-tools is outdated and does not work with Java 1.8. Download Proguard 6.0.3 or later (last tested with 7.0.1) from https://github.com/Guardsquare/proguard
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard-4.7/.
- Go to the Android SDK install path. Rename the tools/proguard/ path to tools/proguard4.7/. - Extract your Proguard version to the Android SDK install path under tools/. You will need to either rename the dir proguard-<your-version> to proguard/ or, if your filesystem supports it, use a symbolic link (the later is highly recommended), such as `ln -s proguard proguard-<your-version>`.
- Extract Proguard 6.0.3 to the Android SDK install path under tools/. You will need to rename the dir proguard6.0.3/ to proguard/. #### Android Build
### Android Build
The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace The Eclipse plug-ins do NOT support building things for Android. They do however allow you to use the debugger so you can still set breakpoints and trace
things out. The steps below show how to generate a debug Android build. things out. The steps below show how to generate a debug Android build.
@@ -135,7 +134,7 @@ things out. The steps below show how to generate a debug Android build.
Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path. Assuming you got this far, you should have an Android forge-android-[version].apk in the forge-gui-android/target path.
### Android Deploy #### Android Deploy
You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds. You'll need to have the Android SDK install path platform-tools/ path in your command search path to easily deploy builds.
@@ -149,14 +148,14 @@ You'll need to have the Android SDK install path platform-tools/ path in your co
- Install the new apk: `adb install forge-android-[version].apk` - Install the new apk: `adb install forge-android-[version].apk`
### Android Debugging #### Android Debugging
Assuming the apk is installed, launch it from the device. Assuming the apk is installed, launch it from the device.
In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a In Eclipse, launch the DDMS. Window > Perspective > Open Perspective > Other... > DDMS. You should see the forge app in the list. Highlight the app, click on the green debug button and a
green debug button should appear next to the app's name. You can now set breakpoints and step through the source code. green debug button should appear next to the app's name. You can now set breakpoints and step through the source code.
## Windows / Linux SNAPSHOT build ### Windows / Linux SNAPSHOT build
SNAPSHOT builds can be built via the Maven integration in Eclipse. SNAPSHOT builds can be built via the Maven integration in Eclipse.
@@ -167,19 +166,19 @@ SNAPSHOT builds can be built via the Maven integration in Eclipse.
The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT The resulting snapshot will be found at: forge-gui-desktop/target/forge-gui-desktop-[version]-SNAPSHOT
# IntelliJ ## IntelliJ
Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup). Quick start guide for [setting up the Forge project within IntelliJ](https://git.cardforge.org/core-developers/forge/wikis/intellij-setup).
# Card Scripting ## Card Scripting
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting. Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
Card scripting resources are found in the forge-gui/res/ path. Card scripting resources are found in the forge-gui/res/ path.
# General Notes ## General Notes
## Project Hierarchy ### Project Hierarchy
Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are: Forge is divided into 4 primary projects with additional projects that target specific platform releases. The primary projects are:
@@ -196,35 +195,34 @@ The platform-specific projects are:
- forge-gui-mobile - forge-gui-mobile
- forge-gui-mobile-dev - forge-gui-mobile-dev
### forge-ai #### forge-ai
### forge-core #### forge-core
### forge-game #### forge-game
### forge-gui #### forge-gui
The forge-gui project includes the scripting resource definitions in the res/ path. The forge-gui project includes the scripting resource definitions in the res/ path.
### forge-gui-android #### forge-gui-android
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic. Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
### forge-gui-desktop #### forge-gui-desktop
Java Swing based GUI targeting desktop machines. Java Swing based GUI targeting desktop machines.
Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this. Screen layout and game logic revolving around the GUI is found here. For example, the overlay arrows (when enabled) that indicate attackers and blockers, or the targets of the stack are defined and drawn by this.
### forge-gui-ios #### forge-gui-ios
Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic. Libgdx-based backend targeting iOS. Relies on forge-gui-mobile for GUI logic.
### forge-gui-mobile #### forge-gui-mobile
Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here. Mobile GUI game logic utilizing [libgdx](https://libgdx.badlogicgames.com/) library. Screen layout and game logic revolving around the GUI for the mobile platforms is found here.
### forge-gui-mobile-dev #### forge-gui-mobile-dev
Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic. Libgdx backend for desktop development for mobile backends. Utilizes LWJGL. Relies on forge-gui-mobile for GUI logic.

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.40-SNAPSHOT</version> <version>1.6.40</version>
</parent> </parent>
<artifactId>forge-ai</artifactId> <artifactId>forge-ai</artifactId>

View File

@@ -91,7 +91,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, boolean nextTurn) { public AiAttackController(final Player ai, boolean nextTurn) {
this.ai = ai; this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(); this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent); this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>(); this.attackers = new ArrayList<>();
@@ -107,7 +107,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) { public AiAttackController(final Player ai, Card attacker) {
this.ai = ai; this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(); this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent); this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>(); this.attackers = new ArrayList<>();
@@ -156,13 +156,12 @@ public class AiAttackController {
} }
/** Choose opponent for AI to attack here. Expand as necessary. */ /** Choose opponent for AI to attack here. Expand as necessary. */
private Player choosePreferredDefenderPlayer() { public static Player choosePreferredDefenderPlayer(Player ai) {
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
return defender; // TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
} else { //Otherwise choose a random opponent to ensure no ganging up on players return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
} }
return defender; return defender;
} }
@@ -624,7 +623,7 @@ public class AiAttackController {
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage; int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp); int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife() if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, opp) >= opp.getLife()
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) { && !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
return true; return true;
} }
@@ -919,7 +918,7 @@ public class AiAttackController {
// find the potential damage ratio the AI can cause // find the potential damage ratio the AI can cause
double humanLifeToDamageRatio = 1000000; double humanLifeToDamageRatio = 1000000;
if (candidateUnblockedDamage > 0) { if (candidateUnblockedDamage > 0) {
humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage; humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
} }
// determine if the ai outnumbers the player // determine if the ai outnumbers the player

View File

@@ -32,6 +32,7 @@ import com.google.common.collect.Lists;
import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi; import forge.ai.ability.ExploreAi;
import forge.ai.ability.LearnAi;
import forge.ai.simulation.SpellAbilityPicker; import forge.ai.simulation.SpellAbilityPicker;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
@@ -88,6 +89,7 @@ import forge.game.spellability.SpellAbilityCondition;
import forge.game.spellability.SpellAbilityPredicates; import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.SpellPermanent; import forge.game.spellability.SpellPermanent;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
@@ -826,8 +828,11 @@ public class AiController {
} }
return canPlayFromEffectAI((SpellPermanent)sa, false, true); return canPlayFromEffectAI((SpellPermanent)sa, false, true);
} }
if (sa.usesTargeting() && !sa.isTargetNumberValid()) { if (sa.usesTargeting()) {
if (!sa.getTargetRestrictions().hasCandidates(sa, true)) { if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
return AiPlayDecision.TargetingFailed;
}
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
return AiPlayDecision.TargetingFailed; return AiPlayDecision.TargetingFailed;
} }
} }
@@ -1110,12 +1115,18 @@ public class AiController {
final CardCollection discardList = new CardCollection(); final CardCollection discardList = new CardCollection();
int count = 0; int count = 0;
if (sa != null) { if (sa != null) {
String logic = sa.getParamOrDefault("AILogic", "");
sourceCard = sa.getHostCard(); sourceCard = sa.getHostCard();
if ("Always".equals(sa.getParam("AILogic")) && !validCards.isEmpty()) { if ("Always".equals(logic) && !validCards.isEmpty()) {
min = 1; min = 1;
} else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) { } else if (logic.startsWith("UnlessAtLife.")) {
int threshold = AbilityUtils.calculateAmount(sourceCard, logic.substring(logic.indexOf(".") + 1), sa);
if (player.getLife() <= threshold) {
min = 1;
}
} else if ("VolrathsShapeshifter".equals(logic)) {
return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa); return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa);
} else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) { } else if ("DiscardCMCX".equals(logic)) {
final int cmc = sa.getXManaCostPaid(); final int cmc = sa.getXManaCostPaid();
CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)); CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc));
if (discards.isEmpty()) { if (discards.isEmpty()) {
@@ -2089,8 +2100,11 @@ public class AiController {
if (useSimulation) { if (useSimulation) {
return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); return simPicker.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
} }
if (sa.getApi() == ApiType.Explore) { if (sa.getApi() == ApiType.Explore) {
return ExploreAi.shouldPutInGraveyard(fetchList, decider); return ExploreAi.shouldPutInGraveyard(fetchList, decider);
} else if (sa.getApi() == ApiType.Learn) {
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
} else { } else {
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
} }

View File

@@ -6,6 +6,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import forge.game.cost.*;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -23,36 +24,6 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.cost.CostAddMana;
import forge.game.cost.CostChooseCreatureType;
import forge.game.cost.CostDamage;
import forge.game.cost.CostDecisionMakerBase;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostDraw;
import forge.game.cost.CostExert;
import forge.game.cost.CostExile;
import forge.game.cost.CostExileFromStack;
import forge.game.cost.CostExiledMoveToGrave;
import forge.game.cost.CostFlipCoin;
import forge.game.cost.CostGainControl;
import forge.game.cost.CostGainLife;
import forge.game.cost.CostMill;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostPayEnergy;
import forge.game.cost.CostPayLife;
import forge.game.cost.CostPutCardToLib;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveAnyCounter;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostReturn;
import forge.game.cost.CostReveal;
import forge.game.cost.CostSacrifice;
import forge.game.cost.CostTap;
import forge.game.cost.CostTapType;
import forge.game.cost.CostUnattach;
import forge.game.cost.CostUntap;
import forge.game.cost.CostUntapType;
import forge.game.cost.PaymentDecision;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -577,6 +548,11 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability)); return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
} }
@Override
public PaymentDecision visit(CostRevealChosenPlayer cost) {
return PaymentDecision.number(1);
}
protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) { protected int removeCounter(GameEntityCounterTable table, List<Card> prefs, CounterEnumType cType, int stillToRemove) {
int removed = 0; int removed = 0;
if (!prefs.isEmpty() && stillToRemove > 0) { if (!prefs.isEmpty() && stillToRemove > 0) {
@@ -590,7 +566,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove); int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove);
if (thisRemove > 0) { if (thisRemove > 0) {
removed += thisRemove; removed += thisRemove;
table.put(prefCard, CounterType.get(cType), thisRemove); table.put(null, prefCard, CounterType.get(cType), thisRemove);
} }
} }
} }
@@ -656,7 +632,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(card.getCounters(ctype), c - toRemove); int thisRemove = Math.min(card.getCounters(ctype), c - toRemove);
if (thisRemove > 0) { if (thisRemove > 0) {
toRemove += thisRemove; toRemove += thisRemove;
table.put(card, ctype, thisRemove); table.put(null, card, ctype, thisRemove);
} }
} }
} }
@@ -684,7 +660,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int over = Math.min(e.getValue(), c - toRemove); int over = Math.min(e.getValue(), c - toRemove);
if (over > 0) { if (over > 0) {
toRemove += over; toRemove += over;
table.put(crd, e.getKey(), over); table.put(null, crd, e.getKey(), over);
} }
} }
} }
@@ -714,7 +690,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int over = Math.min(e.getValue(), c - toRemove); int over = Math.min(e.getValue(), c - toRemove);
if (over > 0) { if (over > 0) {
toRemove += over; toRemove += over;
table.put(crd, e.getKey(), over); table.put(null, crd, e.getKey(), over);
} }
} }
} }
@@ -754,7 +730,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove); int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove);
if (over > 0) { if (over > 0) {
toRemove += over; toRemove += over;
table.put(crd, CounterType.get(CounterEnumType.QUEST), over); table.put(null, crd, CounterType.get(CounterEnumType.QUEST), over);
} }
} }
} }
@@ -778,7 +754,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove); int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove);
if (thisRemove > 0) { if (thisRemove > 0) {
toRemove += thisRemove; toRemove += thisRemove;
table.put(card, cost.counter, thisRemove); table.put(null, card, cost.counter, thisRemove);
} }
} }
} }
@@ -792,7 +768,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
int thisRemove = Math.min(e.getValue(), c - toRemove); int thisRemove = Math.min(e.getValue(), c - toRemove);
if (thisRemove > 0) { if (thisRemove > 0) {
toRemove += thisRemove; toRemove += thisRemove;
table.put(card, e.getKey(), thisRemove); table.put(null, card, e.getKey(), thisRemove);
} }
} }
} }

View File

@@ -188,7 +188,6 @@ public class ComputerUtil {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
// Play higher costing spells first? // Play higher costing spells first?
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
@@ -213,7 +212,8 @@ public class ComputerUtil {
if (unless != null && !unless.endsWith(">")) { if (unless != null && !unless.endsWith(">")) {
final int amount = AbilityUtils.calculateAmount(source, unless, sa); final int amount = AbilityUtils.calculateAmount(source, unless, sa);
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size(); // this is enough as long as the AI is only smart enough to target top of stack
final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtilAbility.getTopSpellAbilityOnStack(ai.getGame(), sa).getActivatingPlayer(), true).size();
// If the Unless isn't enough, this should be less likely to be used // If the Unless isn't enough, this should be less likely to be used
if (amount > usableManaSources) { if (amount > usableManaSources) {
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
return true; return true;
} }
} }
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
return true;
}
if (card.isCreature()) { if (card.isCreature()) {
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) { if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
return true; return true;
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
} // BuffedBy } // BuffedBy
// get all cards the human controls with AntiBuffedBy // there's a good chance AI will attack weak target
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield); final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) { for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) { if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy"); final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1142,27 +1139,16 @@ public class ComputerUtil {
* @return true if it's OK to cast this Card for less than the max targets * @return true if it's OK to cast this Card for less than the max targets
*/ */
public static boolean shouldCastLessThanMax(final Player ai, final Card source) { public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
boolean ret = true; if (source.getXManaCostPaid() > 0) {
if (source.getManaCost().countX() > 0) { // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. return true;
return ret; }
} else { if (aiLifeInDanger(ai, false, 0)) {
// Otherwise, if life is possibly in danger, then this is fine. // Otherwise, if life is possibly in danger, then this is fine.
Combat combat = new Combat(ComputerUtil.getOpponentFor(ai)); return true;
CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
for (Card att : attackers) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
} }
} // do not play now.
AiBlockController aiBlock = new AiBlockController(ai); return false;
aiBlock.assignBlockersForCombat(combat);
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
// Otherwise, return false. Do not play now.
ret = false;
}
}
return ret;
} }
/** /**
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
} }
} }
// get all cards the human controls with AntiBuffedBy // there's a good chance AI will attack weak target
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield); final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) { for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) { if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy"); final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
return false; return false;
} }
public static int possibleNonCombatDamage(Player ai) { public static int possibleNonCombatDamage(Player ai, Player enemy) {
int damage = 0; int damage = 0;
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true)); all.addAll(ai.getCardsActivableInExternalZones(true));
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
if (tgt == null) { if (tgt == null) {
continue; continue;
} }
final Player enemy = ComputerUtil.getOpponentFor(ai);
if (!sa.canTarget(enemy)) { if (!sa.canTarget(enemy)) {
continue; continue;
} }
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
} }
} }
else if (logic.equals("ChosenLandwalk")) { else if (logic.equals("ChosenLandwalk")) {
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) { for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType()) { for (String t : c.getType()) {
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) { if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
chosen = t; chosen = t;
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
else if (kindOfType.equals("Land")) { else if (kindOfType.equals("Land")) {
if (logic != null) { if (logic != null) {
if (logic.equals("ChosenLandwalk")) { if (logic.equals("ChosenLandwalk")) {
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) { for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType().getLandTypes()) { for (String t : c.getType().getLandTypes()) {
if (!invalidTypes.contains(t)) { if (!invalidTypes.contains(t)) {
chosen = t; chosen = t;
@@ -2399,15 +2384,18 @@ public class ComputerUtil {
case "Torture": case "Torture":
return "Torture"; return "Torture";
case "GraceOrCondemnation": case "GraceOrCondemnation":
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace" List<ZoneType> graceZones = new ArrayList<ZoneType>();
: "Condemnation"; graceZones.add(ZoneType.Battlefield);
graceZones.add(ZoneType.Graveyard);
CardCollection graceCreatures = CardLists.getType(sa.getHostCard().getGame().getCardsIn(graceZones), "Creature");
int humanGrace = CardLists.filterControlledBy(graceCreatures, ai.getOpponents()).size();
int aiGrace = CardLists.filterControlledBy(graceCreatures, ai).size();
return aiGrace > humanGrace ? "Grace" : "Condemnation";
case "CarnageOrHomage": case "CarnageOrHomage":
CardCollection cardsInPlay = CardLists CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents()); CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai); CardCollection computerlist = ai.getCreaturesInPlay();
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
case "Judgment": case "Judgment":
if (votes.isEmpty()) { if (votes.isEmpty()) {
CardCollection list = new CardCollection(); CardCollection list = new CardCollection();
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
return true; return true;
} }
@Deprecated
public static final Player getOpponentFor(final Player player) {
// This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
// until it can be replaced everywhere in the code.
// Consider replacing calls to this method either with a multiplayer-friendly determination of
// opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
// where that is applicable and makes sense from the point of view of multiplayer AI logic.
Player opponent = player.getWeakestOpponent();
if (opponent != null) {
return opponent;
}
throw new IllegalStateException("No opponents left ingame for " + player);
}
public static int countUsefulCreatures(Player p) { public static int countUsefulCreatures(Player p) {
CardCollection creats = p.getCreaturesInPlay(); CardCollection creats = p.getCreaturesInPlay();
int count = 0; int count = 0;
@@ -3033,7 +3004,7 @@ public class ComputerUtil {
// call this to determine if it's safe to use a life payment spell // call this to determine if it's safe to use a life payment spell
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell. // or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) { public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
Player opponent = ComputerUtil.getOpponentFor(ai); for (Player opponent: ai.getOpponents()) {
// test whether the human can kill the ai next turn // test whether the human can kill the ai next turn
Combat combat = new Combat(opponent); Combat combat = new Combat(opponent);
boolean containsAttacker = false; boolean containsAttacker = false;
@@ -3059,6 +3030,7 @@ public class ComputerUtil {
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) { if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
return true; return true;
} }
}
return false; return false;
} }

View File

@@ -550,7 +550,7 @@ public class ComputerUtilCard {
*/ */
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) { public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
AiBlockController aiBlk = new AiBlockController(ai); AiBlockController aiBlk = new AiBlockController(ai);
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Combat combat = new Combat(opp); Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers //Use actual attackers if available, else consider all possible attackers
Combat currentCombat = ai.getGame().getCombat(); Combat currentCombat = ai.getGame().getCombat();

View File

@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
* canAttackNextTurn. * canAttackNextTurn.
* </p> * </p>
* *
* @param atacker * @param attacker
* a {@link forge.game.card.Card} object. * a {@link forge.game.card.Card} object.
* @param defender * @param defender
* the defending {@link GameEntity}. * the defending {@link GameEntity}.
* @return a boolean. * @return a boolean.
*/ */
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) { public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
if (!atacker.isCreature()) { if (!attacker.isCreature()) {
return false; return false;
} }
if (!CombatUtil.canAttackNextTurn(atacker, defender)) { if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
return false; return false;
} }
for (final KeywordInterface inst : atacker.getKeywords()) { for (final KeywordInterface inst : attacker.getKeywords()) {
final String keyword = inst.getOriginal(); final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1]; final String defined = keyword.split(":")[1];
final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0); final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
if (!defender.equals(player)) { if (!defender.equals(player)) {
return false; return false;
} }
} }
} }
// TODO this should be a factor but needs some alignment with AttachAi
//boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)
// && ((attacker.hasKeyword(Keyword.VANISHING) && attacker.getCounters(CounterEnumType.TIME) == 1)
// || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
// || attacker.hasSVar("EndOfTurnLeavePlay"));
// The creature won't untap next turn // The creature won't untap next turn
return !atacker.isTapped() || Untap.canUntap(atacker); return !attacker.isTapped() || Untap.canUntap(attacker);
} // canAttackNextTurn(Card, GameEntity) }
/** /**
* <p> * <p>

View File

@@ -612,7 +612,7 @@ public class ComputerUtilCost {
// if payer can't lose life its no need to pay unless // if payer can't lose life its no need to pay unless
if (!payer.canLoseLife()) if (!payer.canLoseLife())
return false; return false;
else if (payer.getLife() <= Integer.valueOf(aiLogic.substring(6))) { else if (payer.getLife() <= AbilityUtils.calculateAmount(source, aiLogic.substring(6), sa)) {
return true; return true;
} }
} else if ("WillAttack".equals(aiLogic)) { } else if ("WillAttack".equals(aiLogic)) {
@@ -684,14 +684,14 @@ public class ComputerUtilCost {
public static int getMaxXValue(SpellAbility sa, Player ai) { public static int getMaxXValue(SpellAbility sa, Player ai) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
final Cost abCost = sa.getPayCosts(); final Cost abCost = root.getPayCosts();
if (abCost == null || !abCost.hasXInAnyCostPart()) { if (abCost == null || !abCost.hasXInAnyCostPart()) {
return 0; return 0;
} }
Integer val = null; Integer val = null;
if (sa.costHasManaX()) { if (root.costHasManaX()) {
val = ComputerUtilMana.determineLeftoverMana(root, ai); val = ComputerUtilMana.determineLeftoverMana(root, ai);
} }

View File

@@ -341,7 +341,7 @@ public class ComputerUtilMana {
continue; continue;
} }
if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts)) { if (canPayShardWithSpellAbility(toPay, ai, paymentChoice, sa, checkCosts, cost.getXManaCostPaidByColor())) {
return paymentChoice; return paymentChoice;
} }
} }
@@ -547,7 +547,7 @@ public class ComputerUtilMana {
} }
// get a mana of this type from floating, bail if none available // get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1); final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), (byte) -1, cost.getXManaCostPaidByColor());
if (mana != null) { if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) { if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, false)) {
manaSpentToPay.add(0, mana); manaSpentToPay.add(0, mana);
@@ -936,7 +936,7 @@ public class ComputerUtilMana {
} }
// get a mana of this type from floating, bail if none available // get a mana of this type from floating, bail if none available
final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1); final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction(), hasConverge ? cost.getColorsPaid() : -1, cost.getXManaCostPaidByColor());
if (mana != null) { if (mana != null) {
if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) { if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana, test)) {
manaSpentToPay.add(0, mana); manaSpentToPay.add(0, mana);
@@ -965,8 +965,10 @@ public class ComputerUtilMana {
* a {@link forge.game.spellability.SpellAbility} object. * a {@link forge.game.spellability.SpellAbility} object.
* @return a {@link forge.game.mana.Mana} object. * @return a {@link forge.game.mana.Mana} object.
*/ */
private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) { private static Mana getMana(final Player ai, final ManaCostShard shard, final SpellAbility saBeingPaidFor,
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard, saBeingPaidFor, restriction, colorsPaid); String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = selectManaToPayFor(ai.getManaPool(), shard,
saBeingPaidFor, restriction, colorsPaid, xManaCostPaidByColor);
// Exclude border case // Exclude border case
if (weightedOptions.isEmpty()) { if (weightedOptions.isEmpty()) {
@@ -1015,9 +1017,13 @@ public class ComputerUtilMana {
} }
private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard, private static List<Pair<Mana, Integer>> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) { final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid, Map<String, Integer> xManaCostPaidByColor) {
final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>(); final List<Pair<Mana, Integer>> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) { for (final Mana thisMana : manapool) {
if (shard == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(thisMana.getColor()), xManaCostPaidByColor)) {
continue;
}
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) { if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
continue; continue;
} }
@@ -1093,7 +1099,7 @@ public class ComputerUtilMana {
} }
} }
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) { private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts, Map<String, Integer> xManaCostPaidByColor) {
final Card sourceCard = ma.getHostCard(); final Card sourceCard = ma.getHostCard();
if (isManaSourceReserved(ai, sourceCard, sa)) { if (isManaSourceReserved(ai, sourceCard, sa)) {
@@ -1131,6 +1137,10 @@ public class ComputerUtilMana {
if (m.isComboMana()) { if (m.isComboMana()) {
for (String s : m.getComboColors().split(" ")) { for (String s : m.getComboColors().split(" ")) {
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
continue;
}
if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s))) if ("Any".equals(s) || ai.getManaPool().canPayForShardWithColor(toPay, ManaAtom.fromName(s)))
return true; return true;
} }
@@ -1141,6 +1151,9 @@ public class ComputerUtilMana {
Set<String> reflected = CardUtil.getReflectableManaColors(ma); Set<String> reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) { for (byte c : MagicColor.WUBRG) {
if (toPay == ManaCostShard.COLORED_X && !ManaCostBeingPaid.canColoredXShardBePaidByColor(MagicColor.toShortString(c), xManaCostPaidByColor)) {
continue;
}
if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) { if (ai.getManaPool().canPayForShardWithColor(toPay, c) && reflected.contains(MagicColor.toLongString(c))) {
m.setExpressChoice(MagicColor.toShortString(c)); m.setExpressChoice(MagicColor.toShortString(c));
return true; return true;
@@ -1148,6 +1161,16 @@ public class ComputerUtilMana {
} }
return false; return false;
} }
if (toPay == ManaCostShard.COLORED_X) {
for (String s : m.mana().split(" ")) {
if (ManaCostBeingPaid.canColoredXShardBePaidByColor(s, xManaCostPaidByColor)) {
return true;
}
}
return false;
}
return true; return true;
} }
@@ -1434,17 +1457,26 @@ public class ComputerUtilMana {
// Tack xMana Payments into mana here if X is a set value // Tack xMana Payments into mana here if X is a set value
if (cost.getXcounter() > 0 || extraMana > 0) { if (cost.getXcounter() > 0 || extraMana > 0) {
int manaToAdd = 0; int manaToAdd = 0;
int xCounter = cost.getXcounter();
if (test && extraMana > 0) { if (test && extraMana > 0) {
final int multiplicator = Math.max(cost.getXcounter(), 1); final int multiplicator = Math.max(xCounter, 1);
manaToAdd = extraMana * multiplicator; manaToAdd = extraMana * multiplicator;
} else { } else {
manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * cost.getXcounter(); manaToAdd = AbilityUtils.calculateAmount(card, "X", sa) * xCounter;
} }
cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd); String xColor = sa.getParamOrDefault("XColor", "1");
if (card.hasKeyword("Spend only colored mana on X. No more than one mana of each color may be spent this way.")) {
xColor = "WUBRGX";
}
if (xCounter > 0) {
cost.setXManaCostPaid(manaToAdd / xCounter, xColor);
} else {
cost.increaseShard(ManaCostShard.parseNonGeneric(xColor), manaToAdd);
}
if (!test) { if (!test) {
sa.setXManaCostPaid(manaToAdd / cost.getXcounter()); sa.setXManaCostPaid(manaToAdd / xCounter);
} }
} }
@@ -1531,7 +1563,7 @@ public class ComputerUtilMana {
public boolean apply(final Card c) { public boolean apply(final Card c) {
for (final SpellAbility am : getAIPlayableMana(c)) { for (final SpellAbility am : getAIPlayableMana(c)) {
am.setActivatingPlayer(ai); am.setActivatingPlayer(ai);
if (!checkPlayable || am.canPlay()) { if (!checkPlayable || (am.canPlay() && am.checkRestrictions(ai))) {
return true; return true;
} }
} }

View File

@@ -3,23 +3,12 @@ package forge.ai;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import com.google.common.collect.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import forge.StaticData; import forge.StaticData;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.MagicColor; import forge.card.MagicColor;
@@ -27,7 +16,6 @@ import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.effects.DetachedCardEffect; import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCloneStates; import forge.game.card.CardCloneStates;
@@ -44,9 +32,7 @@ import forge.game.mana.ManaPool;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.IPaperCard; import forge.item.IPaperCard;
@@ -770,23 +756,10 @@ public abstract class GameState {
} }
game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap)); game.fireEvent(new GameEventAttackersDeclared(attackingPlayer, attackersMap));
if (!combat.getAttackers().isEmpty()) {
List<GameEntity> attackedTarget = Lists.newArrayList();
for (final Card c : combat.getAttackers()) { for (final Card c : combat.getAttackers()) {
attackedTarget.add(combat.getDefenderByAttacker(c)); CombatUtil.checkDeclaredAttacker(game, c, combat, false);
}
final Map<AbilityKey, Object> runParams = Maps.newEnumMap(AbilityKey.class);
runParams.put(AbilityKey.Attackers, combat.getAttackers());
runParams.put(AbilityKey.AttackingPlayer, combat.getAttackingPlayer());
runParams.put(AbilityKey.AttackedTarget, attackedTarget);
game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false);
} }
for (final Card c : combat.getAttackers()) {
CombatUtil.checkDeclaredAttacker(game, c, combat);
}
game.getTriggerHandler().resetActiveTriggers();
game.updateCombatForView(); game.updateCombatForView();
game.fireEvent(new GameEventCombatChanged()); game.fireEvent(new GameEventCombatChanged());
@@ -1346,7 +1319,7 @@ public abstract class GameState {
} }
else if (info.startsWith("OnAdventure")) { else if (info.startsWith("OnAdventure")) {
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c); SpellAbility saAdventure = AbilityFactory.getAbility(abAdventure, c);
StringBuilder sbPlay = new StringBuilder(); StringBuilder sbPlay = new StringBuilder();
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure"); sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card."); sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");

View File

@@ -530,7 +530,14 @@ public class PlayerControllerAi extends PlayerController {
@Override @Override
public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) { public CardCollectionView chooseCardsToDiscardUnlessType(int num, CardCollectionView hand, String uType, SpellAbility sa) {
final CardCollectionView cardsOfType = CardLists.getType(hand, uType); String [] splitUTypes = uType.split(",");
CardCollection cardsOfType = new CardCollection();
for (String part : splitUTypes) {
CardCollection partCards = CardLists.getType(hand, part);
if (!partCards.isEmpty()) {
cardsOfType.addAll(partCards);
}
}
if (!cardsOfType.isEmpty()) { if (!cardsOfType.isEmpty()) {
Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc); Card toDiscard = Aggregates.itemWithMin(cardsOfType, CardPredicates.Accessors.fnGetCmc);
return new CardCollection(toDiscard); return new CardCollection(toDiscard);
@@ -1022,7 +1029,7 @@ public class PlayerControllerAi extends PlayerController {
*/ */
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) { if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
if (sa.isSpell()) { if (sa.isSpell()) {
sa.getHostCard().ceaseToExist(); player.getGame().getAction().ceaseToExist(sa.getHostCard(), false);
} }
continue; continue;
} }
@@ -1056,13 +1063,12 @@ public class PlayerControllerAi extends PlayerController {
boolean noManaCost = tgtSA.hasParam("WithoutManaCost"); boolean noManaCost = tgtSA.hasParam("WithoutManaCost");
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell? if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
Spell spell = (Spell) tgtSA; Spell spell = (Spell) tgtSA;
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) { if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
if (noManaCost) { if (noManaCost) {
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame()); return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
} else { }
return ComputerUtil.playStack(tgtSA, player, getGame()); return ComputerUtil.playStack(tgtSA, player, getGame());
} }
} else
return false; // didn't play spell return false; // didn't play spell
} }
return true; return true;
@@ -1086,7 +1092,6 @@ public class PlayerControllerAi extends PlayerController {
boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES); boolean allCreatures = Iterables.all(Iterables.concat(pile1, pile2), CardPredicates.Presets.CREATURES);
int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1); int cmc1 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile1) : ComputerUtilCard.evaluatePermanentList(pile1);
int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2); int cmc2 = allCreatures ? ComputerUtilCard.evaluateCreatureList(pile2) : ComputerUtilCard.evaluatePermanentList(pile2);
System.out.println("value:" + cmc1 + " " + cmc2);
// for now, this assumes that the outcome will be bad // for now, this assumes that the outcome will be bad
// TODO: This should really have a ChooseLogic param to // TODO: This should really have a ChooseLogic param to

View File

@@ -112,6 +112,63 @@ public class SpecialCardAi {
} }
} }
// Brain in a Jar
public static class BrainInAJar {
public static boolean consider(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard();
int counterNum = source.getCounters(CounterEnumType.CHARGE);
// no need for logic
if (counterNum == 0) {
return false;
}
int libsize = ai.getCardsIn(ZoneType.Library).size();
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!hand.isEmpty()) {
// has spell that can be cast in hand with put ability
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
return false;
}
// has spell that can be cast if one counter is removed
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
sa.setXManaCostPaid(1);
return true;
}
}
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!library.isEmpty()) {
// get max cmc of instant or sorceries in the libary
int maxCMC = 0;
for (final Card c : library) {
int v = c.getCMC();
if (c.isSplitCard()) {
v = Math.max(c.getCMC(Card.SplitCMCMode.LeftSplitCMC), c.getCMC(Card.SplitCMCMode.RightSplitCMC));
}
if (v > maxCMC) {
maxCMC = v;
}
}
// there is a spell with more CMC, no need to remove counter
if (counterNum + 1 < maxCMC) {
return false;
}
int maxToRemove = counterNum - maxCMC + 1;
// no Scry 0, even if its catched from later stuff
if (maxToRemove <= 0) {
return false;
}
sa.setXManaCostPaid(maxToRemove);
} else {
// no Instant or Sorceries anymore, just scry
sa.setXManaCostPaid(Math.min(counterNum, libsize));
}
return true;
}
}
// Chain of Acid // Chain of Acid
public static class ChainOfAcid { public static class ChainOfAcid {
public static boolean consider(final Player ai, final SpellAbility sa) { public static boolean consider(final Player ai, final SpellAbility sa) {
@@ -159,8 +216,7 @@ public class SpecialCardAi {
final PhaseHandler ph = ai.getGame().getPhaseHandler(); final PhaseHandler ph = ai.getGame().getPhaseHandler();
final Combat combat = ai.getGame().getCombat(); final Combat combat = ai.getGame().getCombat();
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa); Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
animated.addType("Creature");
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) { if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null); animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
} }
@@ -170,10 +226,6 @@ public class SpecialCardAi {
return isOppEOT || isValuableAttacker || isValuableBlocker; return isOppEOT || isValuableAttacker || isValuableBlocker;
} }
public static SpellAbility considerAnimating(final Player ai, final SpellAbility sa, final List<SpellAbility> options) {
return ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) ? options.get(0) : options.get(1);
}
} }
// Cursed Scroll // Cursed Scroll
@@ -911,6 +963,39 @@ public class SpecialCardAi {
} }
} }
// Multiple Choice
public static class MultipleChoice {
public static boolean consider(final Player ai, final SpellAbility sa) {
int maxX = ComputerUtilCost.getMaxXValue(sa, ai);
if (maxX == 0) {
return false;
}
boolean canScryDraw = maxX >= 1 && ai.getCardsIn(ZoneType.Library).size() >= 3; // TODO: generalize / use profile values
boolean canBounce = maxX >= 2 && !ai.getOpponents().getCreaturesInPlay().isEmpty();
boolean shouldBounce = canBounce && ComputerUtilCard.evaluateCreature(ComputerUtilCard.getWorstCreatureAI(ai.getOpponents().getCreaturesInPlay())) > 210; // 180 is the level of a 4/4 token creature
boolean canMakeToken = maxX >= 3;
boolean canDoAll = maxX >= 4 && canScryDraw && shouldBounce;
if (canDoAll) {
sa.setXManaCostPaid(4);
return true;
} else if (canMakeToken) {
sa.setXManaCostPaid(3);
return true;
} else if (shouldBounce) {
sa.setXManaCostPaid(2);
return true;
} else if (canScryDraw) {
sa.setXManaCostPaid(1);
return true;
}
return false;
}
}
// Necropotence // Necropotence
public static class Necropotence { public static class Necropotence {
public static boolean consider(final Player ai, final SpellAbility sa) { public static boolean consider(final Player ai, final SpellAbility sa) {

View File

@@ -58,6 +58,11 @@ public abstract class SpellAbilityAi {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
if (sa.hasParam("AICheckCanPlayWithDefinedX")) {
// FIXME: can this somehow be simplified without the need for an extra AI hint?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
}
if (!checkConditions(ai, sa, sa.getConditions())) { if (!checkConditions(ai, sa, sa.getConditions())) {
SpellAbility sub = sa.getSubAbility(); SpellAbility sub = sa.getSubAbility();
if (sub != null && !checkConditions(ai, sub, sub.getConditions())) { if (sub != null && !checkConditions(ai, sub, sub.getConditions())) {

View File

@@ -34,6 +34,7 @@ public enum SpellApiToAi {
.put(ApiType.BidLife, BidLifeAi.class) .put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Bond, BondAi.class) .put(ApiType.Bond, BondAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class) .put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.Camouflage, ChooseCardAi.class)
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class) .put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class) .put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeX, AlwaysPlayAi.class) .put(ApiType.ChangeX, AlwaysPlayAi.class)
@@ -94,6 +95,7 @@ public enum SpellApiToAi {
.put(ApiType.Haunt, HauntAi.class) .put(ApiType.Haunt, HauntAi.class)
.put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class) .put(ApiType.ImmediateTrigger, ImmediateTriggerAi.class)
.put(ApiType.Investigate, InvestigateAi.class) .put(ApiType.Investigate, InvestigateAi.class)
.put(ApiType.Learn, LearnAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class) .put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class) .put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.Mana, ManaEffectAi.class) .put(ApiType.Mana, ManaEffectAi.class)

View File

@@ -21,8 +21,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent(); final Player opp = ai.getStrongestOpponent();
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card")); List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) { if (list.isEmpty()) {
@@ -40,12 +39,13 @@ public class ActivateAbilityAi extends SpellAbilityAi {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} }
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return randomReturn; return randomReturn;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent(); final Player opp = ai.getStrongestOpponent();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();

View File

@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityContinuous; import forge.game.staticability.StaticAbilityContinuous;
@@ -243,6 +244,11 @@ public class AnimateAi extends SpellAbilityAi {
return true; return true;
} }
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
}
private boolean animateTgtAI(final SpellAbility sa) { private boolean animateTgtAI(final SpellAbility sa) {
final Player ai = sa.getActivatingPlayer(); final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler(); final PhaseHandler ph = ai.getGame().getPhaseHandler();

View File

@@ -6,6 +6,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -1129,8 +1130,7 @@ public class AttachAi extends SpellAbilityAi {
for (Card target : list) { for (Card target : list) {
for (Trigger t : target.getTriggers()) { for (Trigger t : target.getTriggers()) {
if (t.getMode() == TriggerType.SpellCast) { if (t.getMode() == TriggerType.SpellCast) {
final Map<String, String> params = t.getMapParams(); if ("Card.Self".equals(t.getParam("TargetsValid")) && "You".equals(t.getParam("ValidActivatingPlayer"))) {
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
magnetList.add(target); magnetList.add(target);
break; break;
} }

View File

@@ -13,15 +13,18 @@ public class BalanceAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic"); String logic = sa.getParam("AILogic");
int diff = 0; int diff = 0;
// TODO Add support for multiplayer logic Player opp = aiPlayer.getWeakestOpponent();
final Player opp = aiPlayer.getWeakestOpponent();
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield); final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
for (Player min : aiPlayer.getOpponents()) {
if (min.getCardsIn(ZoneType.Battlefield).size() < opp.getCardsIn(ZoneType.Battlefield).size()) {
opp = min;
}
}
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) { if ("BalanceCreaturesAndLands".equals(logic)) {
// Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting // TODO Copied over from hardcoded Balance. We should be checking value of the lands/creatures for each opponent, not just counting
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() - diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size(); CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() - diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (tgt.canTgtCreature()) { if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa); List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) { if (list.isEmpty()) {
return false; return false;

View File

@@ -10,6 +10,7 @@ import forge.game.card.Card;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
public class ChangeTargetsAi extends SpellAbilityAi { public class ChangeTargetsAi extends SpellAbilityAi {
@@ -40,18 +41,20 @@ public class ChangeTargetsAi extends SpellAbilityAi {
// nothing on stack, so nothing to target // nothing on stack, so nothing to target
return false; return false;
} }
final TargetChoices topTargets = topSa.getTargets();
final Card topHost = topSa.getHostCard();
if (sa.getTargets().size() != 0) { if (sa.getTargets().size() != 0 && sa.isTrigger()) {
// something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed // something was already chosen before (e.g. in response to a trigger - Mizzium Meddler), so just proceed
return true; return true;
} }
if (!topSa.usesTargeting() || topSa.getTargets().getTargetCards().contains(sa.getHostCard())) { if (!topSa.usesTargeting() || topTargets.getTargetCards().contains(sa.getHostCard())) {
// if this does not target at all or already targets host, no need to redirect it again // if this does not target at all or already targets host, no need to redirect it again
return false; return false;
} }
for (Card tgt : topSa.getTargets().getTargetCards()) { for (Card tgt : topTargets.getTargetCards()) {
if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) { if (ComputerUtilAbility.getAbilitySourceName(sa).equals(tgt.getName()) && tgt.getController().equals(aiPlayer)) {
// We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites), // We are already targeting at least one card with the same name (e.g. in presence of 2+ Spellskites),
// no need to retarget again to another one // no need to retarget again to another one
@@ -59,7 +62,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
} }
} }
if (topSa.getHostCard() != null && !topSa.getHostCard().getController().isOpponentOf(aiPlayer)) { if (topHost != null && !topHost.getController().isOpponentOf(aiPlayer)) {
// make sure not to redirect our own abilities // make sure not to redirect our own abilities
return false; return false;
} }
@@ -80,12 +83,22 @@ public class ChangeTargetsAi extends SpellAbilityAi {
ManaCost normalizedMana = manaCost.getNormalizedMana(); ManaCost normalizedMana = manaCost.getNormalizedMana();
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer); boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
&& topSa.getTargets().contains(aiPlayer)) { && topTargets.contains(aiPlayer)) {
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life // do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life
return false; return false;
} }
} }
Card firstCard = topTargets.getFirstTargetedCard();
// if we're not the target don't intervene unless we can steal a buff
if (firstCard != null && !aiPlayer.equals(firstCard.getController()) && !topHost.getController().equals(firstCard.getController()) && !topHost.getController().getAllies().contains(firstCard.getController())) {
return false;
}
Player firstPlayer = topTargets.getFirstTargetedPlayer();
if (firstPlayer != null && !aiPlayer.equals(firstPlayer)) {
return false;
}
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(topSa); sa.getTargets().add(topSa);
return true; return true;

View File

@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.ai.AiAttackController;
import forge.ai.AiBlockController; import forge.ai.AiBlockController;
import forge.ai.AiCardMemory; import forge.ai.AiCardMemory;
import forge.ai.AiController; import forge.ai.AiController;
@@ -57,6 +58,7 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -263,7 +265,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
ZoneType origin = null; ZoneType origin = null;
final Player opponent = ai.getWeakestOpponent(); final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
boolean activateForCost = ComputerUtil.activateForCost(sa, ai); boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) { if (sa.hasParam("Origin")) {
@@ -471,7 +473,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if putting cards from hand to library and parent is drawing cards // if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something: // make sure this will actually do something:
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = aiPlayer.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (tgt != null && tgt.canTgtPlayer()) { if (tgt != null && tgt.canTgtPlayer()) {
boolean isCurse = sa.isCurse(); boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opp)) { if (isCurse && sa.canTarget(opp)) {
@@ -530,7 +532,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
Iterable<Player> pDefined; Iterable<Player> pDefined;
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if ((tgt != null) && tgt.canTgtPlayer()) { if ((tgt != null) && tgt.canTgtPlayer()) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.isCurse()) { if (sa.isCurse()) {
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
@@ -892,7 +894,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore? // TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(xPay); sa.setXManaCostPaid(xPay);
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
} }
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa); CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
@@ -913,9 +914,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (sa.isSpell()) { if (sa.isSpell()) {
list.remove(source); // spells can't target their own source, because it's actually in the stack zone list.remove(source); // spells can't target their own source, because it's actually in the stack zone
} }
//System.out.println("isPreferredTarget " + list);
if (sa.hasParam("AttachedTo")) { if (sa.hasParam("AttachedTo")) {
//System.out.println("isPreferredTarget att " + list);
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
@@ -927,7 +926,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false; return false;
} }
}); });
//System.out.println("isPreferredTarget ok " + list);
} }
if (list.size() < sa.getMinTargets()) { if (list.size() < sa.getMinTargets()) {
@@ -1163,6 +1161,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (!list.isEmpty()) { if (!list.isEmpty()) {
if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) { if (destination.equals(ZoneType.Battlefield) || origin.contains(ZoneType.Battlefield)) {
// filter by MustTarget requirement
CardCollection originalList = new CardCollection(list);
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false); final Card mostExpensive = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, false);
if (mostExpensive.isCreature()) { if (mostExpensive.isCreature()) {
// if a creature is most expensive take the best one // if a creature is most expensive take the best one
@@ -1191,6 +1193,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false; return false;
} }
} }
// Restore original list for next loop if filtered by MustTarget requirement
if (mustTargetFiltered) {
list = originalList;
}
} else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) { } else if (destination.equals(ZoneType.Hand) || destination.equals(ZoneType.Library)) {
List<Card> nonLands = CardLists.getNotType(list, "Land"); List<Card> nonLands = CardLists.getNotType(list, "Land");
// Prefer to pull a creature, generally more useful for AI. // Prefer to pull a creature, generally more useful for AI.
@@ -1482,9 +1489,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
if ("DeathgorgeScavenger".equals(logic)) { if ("DeathgorgeScavenger".equals(logic)) {
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa); return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
} else if ("ExtraplanarLens".equals(logic)) { }
if ("ExtraplanarLens".equals(logic)) {
return SpecialCardAi.ExtraplanarLens.consider(ai, sa); return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
} else if ("ExileCombatThreat".equals(logic)) { }
if ("ExileCombatThreat".equals(logic)) {
return doExileCombatThreatLogic(ai, sa); return doExileCombatThreatLogic(ai, sa);
} }
@@ -1984,11 +1993,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa); toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
} }
if (toPay == 0) { if (toPay == 0 || toPay <= usableManaSources) {
canBeSaved.add(potentialTgt);
}
if (toPay <= usableManaSources) {
canBeSaved.add(potentialTgt); canBeSaved.add(potentialTgt);
} }

View File

@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
@@ -74,10 +75,8 @@ public class ChooseCardAi extends SpellAbilityAi {
return !choices.isEmpty(); return !choices.isEmpty();
} else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) { } else if (aiLogic.equals("AtLeast2") || aiLogic.equals("BestBlocker")) {
return choices.size() >= 2; return choices.size() >= 2;
} else if (aiLogic.equals("Clone") || aiLogic.equals("Vesuva")) { } else if (aiLogic.equals("Clone")) {
final String filter = aiLogic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa); choices = CardLists.getValidCards(choices, filter, host.getController(), host, sa);
return !choices.isEmpty(); return !choices.isEmpty();
} else if (aiLogic.equals("Never")) { } else if (aiLogic.equals("Never")) {
@@ -114,7 +113,7 @@ public class ChooseCardAi extends SpellAbilityAi {
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty(); return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
} else if (aiLogic.equals("Duneblast")) { } else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay(); CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay(); CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE); aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE); oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
@@ -172,18 +171,13 @@ public class ChooseCardAi extends SpellAbilityAi {
options = CardLists.filter(options, Presets.UNTAPPED); options = CardLists.filter(options, Presets.UNTAPPED);
} }
choice = ComputerUtilCard.getBestCreatureAI(options); choice = ComputerUtilCard.getBestCreatureAI(options);
} else if (logic.equals("Clone") || logic.equals("Vesuva")) { } else if (logic.equals("Clone")) {
final String filter = logic.equals("Clone") ? "Permanent.YouDontCtrl,Permanent.nonLegendary" : final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary";
"Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva";
CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa);
if (!newOptions.isEmpty()) { if (!newOptions.isEmpty()) {
options = newOptions; options = newOptions;
} }
choice = ComputerUtilCard.getBestAI(options); choice = ComputerUtilCard.getBestAI(options);
if (logic.equals("Vesuva") && "Vesuva".equals(choice.getName())) {
choice = null;
}
} else if ("RandomNonLand".equals(logic)) { } else if ("RandomNonLand".equals(logic)) {
options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa); options = CardLists.getValidCards(options, "Card.nonLand", host.getController(), host, sa);
choice = Aggregates.random(options); choice = Aggregates.random(options);

View File

@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.StaticData; import forge.StaticData;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
@@ -34,9 +35,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
} }
String logic = sa.getParam("AILogic"); String logic = sa.getParam("AILogic");
if (logic.equals("MomirAvatar")) { if (logic.equals("CursedScroll")) {
return SpecialCardAi.MomirVigAvatar.consider(ai, sa);
} else if (logic.equals("CursedScroll")) {
return SpecialCardAi.CursedScroll.consider(ai, sa); return SpecialCardAi.CursedScroll.consider(ai, sa);
} }
@@ -44,7 +43,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) { if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ai.getWeakestOpponent()); sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
} else { } else {
sa.getTargets().add(ai); sa.getTargets().add(ai);
} }

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions(); TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else { } else {

View File

@@ -158,6 +158,22 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
} }
} }
return others; return others;
} else if ("Counters".equals(logic)) {
// TODO: this code will need generalization if this logic is used for cards other
// than Elspeth Conquers Death with different choice parameters
SpellAbility p1p1 = null, loyalty = null;
for (final SpellAbility sp : spells) {
if (("P1P1").equals(sp.getParam("CounterType"))) {
p1p1 = sp;
} else {
loyalty = sp;
}
}
if (sa.getParent().getTargetCard() != null && sa.getParent().getTargetCard().getType().isPlaneswalker()) {
return loyalty;
} else {
return p1p1;
}
} else if ("Fatespinner".equals(logic)) { } else if ("Fatespinner".equals(logic)) {
SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null; SpellAbility skipDraw = null, /*skipMain = null,*/ skipCombat = null;
for (final SpellAbility sp : spells) { for (final SpellAbility sp : spells) {
@@ -363,8 +379,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
} else if ("Riot".equals(logic)) { } else if ("Riot".equals(logic)) {
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1); SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
return preferHasteForRiot(sa, player) ? hasteSA : counterSA; return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
} else if ("CrawlingBarrens".equals(logic)) {
return SpecialCardAi.CrawlingBarrens.considerAnimating(player, sa, spells);
} }
return spells.get(0); // return first choice if no logic found return spells.get(0); // return first choice if no logic found
} }

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions(); TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else { } else {

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else { } else {

View File

@@ -3,13 +3,17 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -95,11 +99,18 @@ public class CloneAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
Card host = sa.getHostCard();
boolean chance = true; boolean chance = true;
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
chance = cloneTgtAI(sa); chance = cloneTgtAI(sa);
} else {
if (sa.hasParam("Choices")) {
CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield),
sa.getParam("Choices"), host.getController(), host, sa);
chance = !choices.isEmpty();
}
} }
// Improve AI for triggers. If source is a creature with: // Improve AI for triggers. If source is a creature with:
@@ -171,18 +182,18 @@ public class CloneAi extends SpellAbilityAi {
@Override @Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
Player targetedPlayer, Map<String, Object> params) { Player targetedPlayer, Map<String, Object> params) {
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
final String name = host.getName();
final Player ctrl = host.getController(); final Player ctrl = host.getController();
final Card cloneTarget = getCloneTarget(sa); final Card cloneTarget = getCloneTarget(sa);
final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer()); final boolean isOpp = cloneTarget.getController().isOpponentOf(sa.getActivatingPlayer());
final boolean isVesuva = "Vesuva".equals(host.getName()); final boolean isVesuva = "Vesuva".equals(name) || "Sculpting Steel".equals(name);
final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary")); final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary"));
String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary" String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary"
: "Permanent.YouDontCtrl+notnamedVesuva,Permanent.nonLegendary+notnamedVesuva"; : "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name;
// TODO: rewrite this block so that this is done somehow more elegantly // TODO: rewrite this block so that this is done somehow more elegantly
if (canCloneLegendary) { if (canCloneLegendary) {
@@ -201,12 +212,13 @@ public class CloneAi extends SpellAbilityAi {
} }
} }
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options); // prevent loop of choosing copy of same card
if (isVesuva) {
if (isVesuva && "Vesuva".equals(choice.getName())) { options = CardLists.filter(options, Predicates.not(CardPredicates.sharesNameWith(host)));
choice = null;
} }
Card choice = isOpp ? ComputerUtilCard.getWorstAI(options) : ComputerUtilCard.getBestAI(options);
return choice; return choice;
} }

View File

@@ -3,6 +3,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
CardCollection list = CardCollection list =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa); CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on // AI won't try to grab cards that are filtered out of AI decks on
// purpose // purpose
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {

View File

@@ -39,6 +39,7 @@ import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates; import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
@@ -211,6 +212,10 @@ public class ControlGainAi extends SpellAbilityAi {
} }
while (t == null) { while (t == null) {
// filter by MustTarget requirement
CardCollection originalList = new CardCollection(list);
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
if (planeswalkers > 0) { if (planeswalkers > 0) {
t = ComputerUtilCard.getBestPlaneswalkerAI(list); t = ComputerUtilCard.getBestPlaneswalkerAI(list);
} else if (creatures > 0) { } else if (creatures > 0) {
@@ -238,6 +243,11 @@ public class ControlGainAi extends SpellAbilityAi {
enchantments--; enchantments--;
} }
// Restore original list for next loop if filtered by MustTarget requirement
if (mustTargetFiltered) {
list = originalList;
}
if (!sa.canTarget(t)) { if (!sa.canTarget(t)) {
list.remove(t); list.remove(t);
t = null; t = null;

View File

@@ -44,7 +44,9 @@ public class CopyPermanentAi extends SpellAbilityAi {
return false; return false;
} }
if ("MimicVat".equals(aiLogic)) { if ("MomirAvatar".equals(aiLogic)) {
return SpecialCardAi.MomirVigAvatar.consider(aiPlayer, sa);
} else if ("MimicVat".equals(aiLogic)) {
return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa); return SpecialCardAi.MimicVat.considerCopy(aiPlayer, sa);
} else if ("AtEOT".equals(aiLogic)) { } else if ("AtEOT".equals(aiLogic)) {
return ph.is(PhaseType.END_OF_TURN); return ph.is(PhaseType.END_OF_TURN);

View File

@@ -372,6 +372,10 @@ public class CountersMoveAi extends SpellAbilityAi {
} }
} }
Card lki = CardUtil.getLKICopy(src);
lki.clearCounters();
// go for opponent when value implies debuff
if (ComputerUtilCard.evaluateCreature(src) > ComputerUtilCard.evaluateCreature(lki)) {
List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai); List<Card> aiList = CardLists.filterControlledBy(tgtCards, ai);
if (!aiList.isEmpty()) { if (!aiList.isEmpty()) {
List<Card> best = CardLists.filter(aiList, new Predicate<Card>() { List<Card> best = CardLists.filter(aiList, new Predicate<Card>() {
@@ -392,7 +396,7 @@ public class CountersMoveAi extends SpellAbilityAi {
if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) { if (cType.is(CounterEnumType.P1P1) && card.hasKeyword(Keyword.UNDYING)) {
return false; return false;
} }
if (cType.is(CounterEnumType.M1M1) && card.hasKeyword(Keyword.PERSIST)) { if (cType.is(CounterEnumType.M1M1)) {
return false; return false;
} }
@@ -415,11 +419,10 @@ public class CountersMoveAi extends SpellAbilityAi {
return true; return true;
} }
} }
}
// move counter to opponents creature but only if you can not steal // move counter to opponents creature but only if you can not steal them
// them // try to move to something useless or something that would leave play
// try to move to something useless or something that would leave
// play
List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents()); List<Card> oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents());
if (!oppList.isEmpty()) { if (!oppList.isEmpty()) {
List<Card> best = CardLists.filter(oppList, new Predicate<Card>() { List<Card> best = CardLists.filter(oppList, new Predicate<Card>() {
@@ -441,7 +444,7 @@ public class CountersMoveAi extends SpellAbilityAi {
}); });
if (best.isEmpty()) { if (best.isEmpty()) {
best = aiList; best = oppList;
} }
Card card = ComputerUtilCard.getBestCreatureAI(best); Card card = ComputerUtilCard.getBestCreatureAI(best);
@@ -455,7 +458,7 @@ public class CountersMoveAi extends SpellAbilityAi {
} }
} }
// used for multiple sources -> defied // used for multiple sources -> defined
// or for source -> multiple defined // or for source -> multiple defined
@Override @Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,

View File

@@ -8,8 +8,10 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiProps;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.card.Card; import forge.game.card.Card;
@@ -121,6 +123,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
final CounterType poison = CounterType.get(CounterEnumType.POISON); final CounterType poison = CounterType.get(CounterEnumType.POISON);
boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO);
// because countertype can't be chosen anymore, only look for posion counters // because countertype can't be chosen anymore, only look for posion counters
for (final Player p : Iterables.filter(options, Player.class)) { for (final Player p : Iterables.filter(options, Player.class)) {
if (p.isOpponentOf(ai)) { if (p.isOpponentOf(ai)) {
@@ -128,7 +131,8 @@ public class CountersProliferateAi extends SpellAbilityAi {
return (T)p; return (T)p;
} }
} else { } else {
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) { // poison is risky, should not proliferate them in most cases
if ((p.getCounters(poison) <= 5 && aggroAI && p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) || !p.canReceiveCounters(poison)) {
return (T)p; return (T)p;
} }
} }

View File

@@ -47,6 +47,8 @@ import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates; import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -313,7 +315,12 @@ public class CountersPutAi extends SpellAbilityAi {
} else if (logic.startsWith("MoveCounter")) { } else if (logic.startsWith("MoveCounter")) {
return doMoveCounterLogic(ai, sa, ph); return doMoveCounterLogic(ai, sa, ph);
} else if (logic.equals("CrawlingBarrens")) { } else if (logic.equals("CrawlingBarrens")) {
return SpecialCardAi.CrawlingBarrens.consider(ai, sa); boolean willActivate = SpecialCardAi.CrawlingBarrens.consider(ai, sa);
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
// don't use this for mana until after combat
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
}
return willActivate;
} }
if (!sa.metConditions() && sa.getSubAbility() == null) { if (!sa.metConditions() && sa.getSubAbility() == null) {
@@ -401,19 +408,37 @@ public class CountersPutAi extends SpellAbilityAi {
} }
if ("Polukranos".equals(logic)) { if ("Polukranos".equals(logic)) {
boolean found = false;
for (Trigger tr : source.getTriggers()) {
if (!tr.getMode().equals(TriggerType.BecomeMonstrous)) {
continue;
}
SpellAbility oa = tr.ensureAbility();
if (oa == null) {
continue;
}
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa); // need to set Activating player
oa.setActivatingPlayer(ai);
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), oa);
if (!targets.isEmpty()){ if (!targets.isEmpty()){
boolean canSurvive = false; boolean canSurvive = false;
for (Card humanCreature : targets) { for (Card humanCreature : targets) {
if (!FightAi.canKill(humanCreature, source, 0)){ if (!FightAi.canKill(humanCreature, source, 0)){
canSurvive = true; canSurvive = true;
break;
} }
} }
if (!canSurvive){ if (!canSurvive){
return false; return false;
} }
};
found = true;
break;
}
if (!found) {
return false;
} }
} }

View File

@@ -14,7 +14,7 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerDamageDone; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -56,7 +56,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
boolean dmgByCardsInHand = false; boolean dmgByCardsInHand = false;
if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) && if ("X".equals(sa.getParam("NumDmg")) && sa.getHostCard() != null && sa.hasSVar(sa.getParam("NumDmg")) &&
sa.getHostCard().getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) { sa.getSVar(sa.getParam("NumDmg")).equals("TargetedPlayer$CardsInHand")) {
dmgByCardsInHand = true; dmgByCardsInHand = true;
} }
// Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size // Not sure if type choice implemented for the AI yet but it should at least recognize this spell hits harder on larger enemy hand size
@@ -75,7 +75,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
// If has triggered ability on dealing damage to an opponent, go for it! // If has triggered ability on dealing damage to an opponent, go for it!
Card hostcard = sa.getHostCard(); Card hostcard = sa.getHostCard();
for (Trigger trig : hostcard.getTriggers()) { for (Trigger trig : hostcard.getTriggers()) {
if (trig instanceof TriggerDamageDone) { if (trig.getMode() == TriggerType.DamageDone) {
if (("Opponent".equals(trig.getParam("ValidTarget"))) if (("Opponent".equals(trig.getParam("ValidTarget")))
&& (!"True".equals(trig.getParam("CombatDamage")))) { && (!"True".equals(trig.getParam("CombatDamage")))) {
return true; return true;

View File

@@ -46,6 +46,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices; import forge.game.spellability.TargetChoices;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Aggregates; import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -343,6 +344,9 @@ public class DamageDealAi extends DamageAiBase {
final Game game = source.getGame(); final Game game = source.getGame();
List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); List<Card> hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game);
// Filter MustTarget requirements
StaticAbilityMustTarget.filterMustTargetCards(ai, hPlay, sa);
CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() { CardCollection killables = CardLists.filter(hPlay, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class DebuffAi extends SpellAbilityAi { public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override @Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
@@ -140,7 +138,6 @@ public class DebuffAi extends SpellAbilityAi {
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) { while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null; Card t = null;
// boolean goodt = false;
if (list.isEmpty()) { if (list.isEmpty()) {
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) { if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
@@ -176,19 +173,18 @@ public class DebuffAi extends SpellAbilityAi {
* @return a CardCollection. * @return a CardCollection.
*/ */
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) { private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa); CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) { if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
// keywords
} }
}); });
} }
return list; return list;
} // getCurseCreatures() }
/** /**
* <p> * <p>
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
list.remove(c); list.remove(c);
} }
final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent()); final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponents());
final CardCollection forced = CardLists.filterControlledBy(list, ai); final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
break; break;
} }
// TODO - if forced targeting, just pick something without the given // TODO - if forced targeting, just pick something without the given keyword
// keyword
Card c; Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) { if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced); c = ComputerUtilCard.getWorstCreatureAI(forced);

View File

@@ -2,15 +2,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.AiController; import forge.ai.*;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi;
import forge.ai.SpecialAiLogic;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
@@ -26,6 +18,7 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class DestroyAi extends SpellAbilityAi { public class DestroyAi extends SpellAbilityAi {
@@ -122,17 +115,21 @@ public class DestroyAi extends SpellAbilityAi {
CardCollection list; CardCollection list;
if (ComputerUtil.preventRunAwayActivations(sa)) { if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; return false;
} }
// Targeting // Targeting
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
// (e.g. Heliod's Intervention)
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
sa.getRootAbility().setXManaCostPaid(xPay);
}
// Assume there where already enough targets chosen by AI Logic Above // Assume there where already enough targets chosen by AI Logic Above
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) { if (sa.hasParam("AILogic") && !sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
return true; return true;
} }
@@ -140,11 +137,11 @@ public class DestroyAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
int maxTargets; int maxTargets;
if (sa.costHasManaX()) { if (sa.getRootAbility().costHasManaX()) {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement. // TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai); maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
// need to set XPaid to get the right number for // need to set XPaid to get the right number for
sa.setXManaCostPaid(maxTargets); sa.getRootAbility().setXManaCostPaid(maxTargets);
// need to check for maxTargets // need to check for maxTargets
maxTargets = Math.min(maxTargets, sa.getMaxTargets()); maxTargets = Math.min(maxTargets, sa.getMaxTargets());
} else { } else {
@@ -226,6 +223,10 @@ public class DestroyAi extends SpellAbilityAi {
// target loop // target loop
// TODO use can add more Targets // TODO use can add more Targets
while (sa.getTargets().size() < maxTargets) { while (sa.getTargets().size() < maxTargets) {
// filter by MustTarget requirement
CardCollection originalList = new CardCollection(list);
boolean mustTargetFiltered = StaticAbilityMustTarget.filterMustTargetCards(ai, list, sa);
if (list.isEmpty()) { if (list.isEmpty()) {
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
sa.resetTargets(); sa.resetTargets();
@@ -286,6 +287,12 @@ public class DestroyAi extends SpellAbilityAi {
} }
} }
} }
// Restore original list for next loop if filtered by MustTarget requirement
if (mustTargetFiltered) {
list = originalList;
}
list.remove(choice); list.remove(choice);
sa.getTargets().add(choice); sa.getTargets().add(choice);
} }

View File

@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
return doMassRemovalLogic(ai, sa); return doMassRemovalLogic(ai, sa);
} }
public boolean doMassRemovalLogic(Player ai, SpellAbility sa) { public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", ""); final String logic = sa.getParamOrDefault("AILogic", "");
Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
final int CREATURE_EVAL_THRESHOLD = 200; // if we hit the whole board, the other opponents who are not the reason to cast this probably still suffer a bit too
final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
if (logic.equals("Always")) { if (logic.equals("Always")) {
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
@@ -93,10 +93,10 @@ public class DestroyAllAi extends SpellAbilityAi {
valid = valid.replace("X", Integer.toString(xPay)); valid = valid.replace("X", Integer.toString(xPay));
} }
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), // TODO should probably sort results when targeted to use on biggest threat instead of first match
valid.split(","), source.getController(), source, sa); for (Player opponent: ai.getOpponents()) {
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
source.getController(), source, sa); CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
opplist = CardLists.filter(opplist, predicate); opplist = CardLists.filter(opplist, predicate);
ailist = CardLists.filter(ailist, predicate); ailist = CardLists.filter(ailist, predicate);
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
return true; return true;
} }
// if only creatures are affected evaluate both lists and pass only if // if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
// human creatures are more valuable
if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) { if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) { if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
return true; return true;
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
return false; return false;
} // only lands involved } // only lands involved
else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) { else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) { if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
return true; return true;
} }
// evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes // evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
@@ -188,4 +187,7 @@ public class DestroyAllAi extends SpellAbilityAi {
} }
return true; return true;
} }
return false;
}
} }

View File

@@ -5,6 +5,7 @@ import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
Player libraryOwner = ai; Player libraryOwner = ai;
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (mandatory && sa.canTarget(opp)) { if (mandatory && sa.canTarget(opp)) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
Player libraryOwner = ai; Player libraryOwner = ai;
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (mandatory && sa.canTarget(opp)) { if (mandatory && sa.canTarget(opp)) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
chance = 1; chance = 1;
} }
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai; Player libraryOwner = ai;
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if ("DontMillSelf".equals(logic)) { if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard // A card that digs for specific things and puts everything revealed before it into graveyard
@@ -92,12 +91,12 @@ public class DigUntilAi extends SpellAbilityAi {
return false; return false;
} }
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
return randomReturn; return randomReturn;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (sa.isCurse()) { if (sa.isCurse()) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
if (players.get(0) == ai) { if (players.get(0) == ai) {
// the ai should only be using something like this if he has // the ai should only be using something like this if he has
// few cards in hand, // few cards in hand,
// cards like this better have a good drawback to be in the // cards like this better have a good drawback to be in the AIs deck
// AIs deck
} else { } else {
// defined to the human, so that's fine as long the human // defined to the human, so that's fine as long the human has cards
// has cards
if (!humanHasHand) { if (!humanHasHand) {
return false; return false;
} }
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (!discardTargetAI(ai, sa)) { if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) { if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);

View File

@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
if (tgt == null) { if (tgt == null) {
// assume we are looking to tap human's stuff // assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap // TODO - check for things with untap abilities, and don't tap those.
// those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) { if (!defined.contains(opp)) {

View File

@@ -19,6 +19,7 @@ import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -50,6 +51,9 @@ public class FightAi extends SpellAbilityAi {
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures); aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay(); List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa); humCreatures = CardLists.getTargetableCards(humCreatures, sa);
// Filter MustTarget requirements
StaticAbilityMustTarget.filterMustTargetCards(ai, humCreatures, sa);
if (humCreatures.isEmpty()) if (humCreatures.isEmpty())
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant

View File

@@ -16,14 +16,32 @@ public class FlipACoinAi extends SpellAbilityAi {
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.hasParam("AILogic")) { if (sa.hasParam("AILogic")) {
String AILogic = sa.getParam("AILogic"); String ailogic = sa.getParam("AILogic");
if (AILogic.equals("Never")) { if (ailogic.equals("Never")) {
return false; return false;
} else if (AILogic.equals("PhaseOut")) { } else if (ailogic.equals("PhaseOut")) {
if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) { if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) {
return false; return false;
} }
} else if (AILogic.equals("KillOrcs")) { } else if (ailogic.equals("Bangchuckers")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false;
}
sa.resetTargets();
for (Player o : ai.getOpponents()) {
if (sa.canTarget(o) && o.canLoseLife() && !o.cantLose()) {
sa.getTargets().add(o);
return true;
}
}
for (Card c : ai.getOpponents().getCreaturesInPlay()) {
if (sa.canTarget(c)) {
sa.getTargets().add(c);
return true;
}
}
return false;
} else if (ailogic.equals("KillOrcs")) {
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) { if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN) ) {
return false; return false;
} }

View File

@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Phage the Untouchable // Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a // (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet) // specific turn, which can't be done yet)
Player opp = ai.getWeakestOpponent(); Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
if (!mandatory && opp.cantLose()) { if (!mandatory && opp.cantLose()) {
return false; return false;

View File

@@ -0,0 +1,56 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class LearnAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
// For the time being, Learn is treated as universally positive due to being optional
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(aiPlayer, sa);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
return canPlayAI(aiPlayer, sa);
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;
}
public static Card chooseCardToLearn(CardCollection options, Player ai, SpellAbility sa) {
CardCollection sideboard = CardLists.filter(options, CardPredicates.inZone(ZoneType.Sideboard));
CardCollection hand = CardLists.filter(options, CardPredicates.inZone(ZoneType.Hand));
hand.remove(sa.getHostCard()); // this card will be used in the process, don't consider it for discard
CardCollection lessons = CardLists.filter(sideboard, CardPredicates.isType("Lesson"));
CardCollection goodDiscards = ((PlayerControllerAi)ai.getController()).getAi().getCardsToDiscard(1, 1, hand, sa);
if (!lessons.isEmpty()) {
return ComputerUtilCard.getBestAI(lessons);
} else if (!goodDiscards.isEmpty()) {
return ComputerUtilCard.getWorstAI(goodDiscards);
}
// Don't choose anything if there's no good option
return null;
}
}

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final int myLife = aiPlayer.getLife(); final int myLife = aiPlayer.getLife();
Player opponent = aiPlayer.getWeakestOpponent(); Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
final int hLife = opponent.getLife(); final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) { if (!aiPlayer.canGainLife()) {
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
final boolean mandatory) { final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.AiProps; import forge.ai.AiProps;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
final boolean mandatory) { final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final int myLife = ai.getLife(); final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent(); final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife(); final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount"); final String amountStr = sa.getParam("LifeAmount");
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
return false; return false;
} }
// TODO handle proper calculation of X values based on Cost and what // TODO handle proper calculation of X values based on Cost and what would be paid
// would be paid
int amount; int amount;
// we shouldn't have to worry too much about PayX for SetLife // we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
if (tgt.canOnlyTgtOpponent()) { if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent); sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life // if we can only target the human, and the Human's life
// would // would go up, don't play it.
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and // possibly add a combo here for Magister Sphinx and
// Higedetsu's // Higedetsu's (sp?) Second Rite
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) { if ((amount > hlife) || !opponent.canLoseLife()) {
return false; return false;
} }
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
if (sa.getParam("Defined").equals("Player")) { if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) { if (amount == 0) {
return false; return false;
} else if (myLife > amount) { // will decrease computer's } else if (myLife > amount) { // will decrease computer's life
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) { if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false; return false;
} }
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife(); final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent(); final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife(); final int hlife = opponent.getLife();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
@@ -133,8 +129,7 @@ public class LifeSetAi extends SpellAbilityAi {
} }
// If the Target is gaining life, target self. // If the Target is gaining life, target self.
// if the Target is modifying how much life is gained, this needs to // if the Target is modifying how much life is gained, this needs to be handled better
// be handled better
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();

View File

@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
List<Card> list = List<Card> list =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa); CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on // AI won't try to grab cards that are filtered out of AI decks on purpose
// purpose
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa); return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
} }
}); });
CardLists.sortByPowerAsc(list); CardLists.sortByPowerAsc(list);

View File

@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final int newPower = card.getNetCombatDamage() + attack; final int newPower = card.getNetCombatDamage() + attack;
//int defense = getNumDefense(sa); //int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) { if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {

View File

@@ -48,12 +48,6 @@ public class PumpAllAi extends PumpAiBase {
} }
} }
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase();
if (ComputerUtil.preventRunAwayActivations(sa)) { if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; return false;
} }
@@ -64,15 +58,9 @@ public class PumpAllAi extends PumpAiBase {
} }
} }
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
final Player opp = ai.getWeakestOpponent();
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getStrongestOpponent();
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) { if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
return true; return true;
} }
final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase();
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
if (!game.getStack().isEmpty() && !sa.isCurse()) { if (!game.getStack().isEmpty() && !sa.isCurse()) {
return pumpAgainstRemoval(ai, sa, comp); return pumpAgainstRemoval(ai, sa, comp);
} }
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
return true; return true;
} }
// evaluate both lists and pass only if human creatures are more // evaluate both lists and pass only if human creatures are more valuable
// valuable
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human); return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
} // end Curse } // end Curse

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.AiController; import forge.ai.AiController;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
if (!opp.canBeTargetedBy(sa)) { if (!opp.canBeTargetedBy(sa)) {
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);

View File

@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
return false; return false;
} else if ("AllPlayerLoseLife".equals(logic)) { } else if ("AllPlayerLoseLife".equals(logic)) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility"); SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
String svar = repeat.getSVar(repeat.getParam("LifeAmount")); String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
// replace RememberedPlayerCtrl with YouCtrl // replace RememberedPlayerCtrl with YouCtrl

View File

@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class SacrificeAi extends SpellAbilityAi { public class SacrificeAi extends SpellAbilityAi {
// **************************************************************
// *************************** Sacrifice ***********************
// **************************************************************
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
@@ -48,8 +45,7 @@ public class SacrificeAi extends SpellAbilityAi {
} }
// Improve AI for triggers. If source is a creature with: // Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something // When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with not // Eventually, we can call the trigger of ETB abilities with not
// mandatory as part of the checks to cast something // mandatory as part of the checks to cast something
@@ -58,12 +54,11 @@ public class SacrificeAi extends SpellAbilityAi {
} }
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) { private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean destroy = sa.hasParam("Destroy"); final boolean destroy = sa.hasParam("Destroy");
Player opp = ai.getWeakestOpponent(); Player opp = ai.getStrongestOpponent();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount)); sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
} }
final int half = (amount / 2) + (amount % 2); // Half of amount final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
// rounded up
// If the Human has at least half rounded up of the amount to be // If the Human has at least half rounded up of the amount to be
// sacrificed, cast the spell // sacrificed, cast the spell
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
// If Sacrifice hits both players: // If Sacrifice hits both players:
// Only cast it if Human has the full amount of valid // Only cast it if Human has the full amount of valid
// Only cast it if AI doesn't have the full amount of Valid // Only cast it if AI doesn't have the full amount of Valid
// TODO: Cast if the type is favorable: my "worst" valid is // TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
// worse than his "worst" valid
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1"; final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
int amount = AbilityUtils.calculateAmount(source, num, sa); int amount = AbilityUtils.calculateAmount(source, num, sa);

View File

@@ -2,17 +2,12 @@ package forge.ai.ability;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil;
public class SacrificeAllAi extends SpellAbilityAi { public class SacrificeAllAi extends SpellAbilityAi {
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
// based on what the expected targets could be // based on what the expected targets could be
final Cost abCost = sa.getPayCosts(); final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
String valid = ""; final String logic = sa.getParamOrDefault("AILogic", "");
if (sa.hasParam("ValidCards")) {
valid = sa.getParam("ValidCards");
}
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
}
CardCollection humanlist =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
CardCollection computerlist =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
if (abCost != null) { if (abCost != null) {
// AI currently disabled for some costs // AI currently disabled for some costs
@@ -46,29 +26,19 @@ public class SacrificeAllAi extends SpellAbilityAi {
} }
} }
if (logic.equals("HellionEruption")) {
if (ai.getCreaturesInPlay().size() < 5 || ai.getCreaturesInPlay().size() * 150 < ComputerUtilCard.evaluateCreatureList(ai.getCreaturesInPlay())) {
return false;
}
}
if (!DestroyAllAi.doMassRemovalLogic(ai, sa)) {
return false;
}
// prevent run-away activations - first time will always return true // prevent run-away activations - first time will always return true
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// if only creatures are affected evaluate both lists and pass only if
// human creatures are more valuable
if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
.evaluateCreatureList(humanlist)) {
return false;
}
} // only lands involved
else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
} // otherwise evaluate both lists by CMC and pass only if human
// permanents are more valuable
else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
.evaluatePermanentList(humanlist)) {
return false;
}
return ((MyRandom.getRandom().nextFloat() < .9667) && chance); return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
} }

View File

@@ -3,14 +3,12 @@ package forge.ai.ability;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.Card.SplitCMCMode;
import forge.game.card.CardCollection;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
@@ -47,6 +45,11 @@ public class ScryAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
// For Brain in a Jar, avoid competing against the other ability in the opponent's EOT.
if ("BrainJar".equals(sa.getParam("AILogic"))) {
return ph.getPhase().isAfter(PhaseType.MAIN2);
}
// if the Scry ability requires tapping and has a mana cost, it's best done at the end of opponent's turn // if the Scry ability requires tapping and has a mana cost, it's best done at the end of opponent's turn
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to // and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible, // try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
@@ -102,56 +105,9 @@ public class ScryAi extends SpellAbilityAi {
if ("Never".equals(aiLogic)) { if ("Never".equals(aiLogic)) {
return false; return false;
} else if ("BrainJar".equals(aiLogic)) { } else if ("BrainJar".equals(aiLogic)) {
final Card source = sa.getHostCard(); return SpecialCardAi.BrainInAJar.consider(ai, sa);
} else if ("MultipleChoice".equals(aiLogic)) {
int counterNum = source.getCounters(CounterEnumType.CHARGE); return SpecialCardAi.MultipleChoice.consider(ai, sa);
// no need for logic
if (counterNum == 0) {
return false;
}
int libsize = ai.getCardsIn(ZoneType.Library).size();
final CardCollection hand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!hand.isEmpty()) {
// has spell that can be cast in hand with put ability
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum + 1)).isEmpty()) {
return false;
}
// has spell that can be cast if one counter is removed
if (!CardLists.filter(hand, CardPredicates.hasCMC(counterNum)).isEmpty()) {
sa.setXManaCostPaid(1);
return true;
}
}
final CardCollection library = CardLists.filter(ai.getCardsIn(ZoneType.Library), Predicates.or(
CardPredicates.isType("Instant"), CardPredicates.isType("Sorcery")));
if (!library.isEmpty()) {
// get max cmc of instant or sorceries in the libary
int maxCMC = 0;
for (final Card c : library) {
int v = c.getCMC();
if (c.isSplitCard()) {
v = Math.max(c.getCMC(SplitCMCMode.LeftSplitCMC), c.getCMC(SplitCMCMode.RightSplitCMC));
}
if (v > maxCMC) {
maxCMC = v;
}
}
// there is a spell with more CMC, no need to remove counter
if (counterNum + 1 < maxCMC) {
return false;
}
int maxToRemove = counterNum - maxCMC + 1;
// no Scry 0, even if its catched from later stuff
if (maxToRemove <= 0) {
return false;
}
sa.setXManaCostPaid(maxToRemove);
} else {
// no Instant or Sorceries anymore, just scry
sa.setXManaCostPaid(Math.min(counterNum, libsize));
}
} }
return true; return true;
} }

View File

@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
// Set PayX here to maximum value. // Set PayX here to maximum value.
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore? // TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai)); sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
} }
sa.resetTargets(); sa.resetTargets();

View File

@@ -5,6 +5,7 @@ import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
* @return a boolean. * @return a boolean.
*/ */
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) { protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Game game = ai.getGame(); final Game game = ai.getGame();
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tapList = CardLists.getTargetableCards(tapList, sa); tapList = CardLists.getTargetableCards(tapList, sa);

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
valid = sa.getParam("ValidCards"); valid = sa.getParam("ValidCards");
} }
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {

View File

@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
// Check if there are any valid targets // Check if there are any valid targets
List<GameObject> targets = new ArrayList<>(); List<GameObject> targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
if (!mandatory && card.isEquipment() && !targets.isEmpty()) { if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
Card newTarget = (Card) targets.get(0); Card newTarget = (Card) targets.get(0);
//don't equip human creatures //don't equip opponent creatures
if (newTarget.getController().equals(opp)) { if (!newTarget.getController().equals(ai)) {
return false; return false;
} }

View File

@@ -6,6 +6,7 @@ import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
Player targetController = ai; Player targetController = ai;
if (sa.isCurse()) { if (sa.isCurse()) {
targetController = ai.getWeakestOpponent(); // TODO search through all opponents, may need to check if different controllers allowed
targetController = AiAttackController.choosePreferredDefenderPlayer(ai);
} }
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa); CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
@@ -149,8 +151,7 @@ public class UntapAi extends SpellAbilityAi {
} }
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED); CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
// filter out enchantments and planeswalkers, their tapped state doesn't // filter out enchantments and planeswalkers, their tapped state doesn't matter.
// matter.
final String[] tappablePermanents = {"Creature", "Land", "Artifact"}; final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa); untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);

View File

@@ -11,6 +11,7 @@ import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ChangeZoneAi;
import forge.ai.ability.ExploreAi; import forge.ai.ability.ExploreAi;
import forge.ai.ability.LearnAi;
import forge.ai.simulation.GameStateEvaluator.Score; import forge.ai.simulation.GameStateEvaluator.Score;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
@@ -423,6 +424,8 @@ public class SpellAbilityPicker {
} }
if (sa.getApi() == ApiType.Explore) { if (sa.getApi() == ApiType.Explore) {
return ExploreAi.shouldPutInGraveyard(fetchList, decider); return ExploreAi.shouldPutInGraveyard(fetchList, decider);
} else if (sa.getApi() == ApiType.Learn) {
return LearnAi.chooseCardToLearn(fetchList, decider, sa);
} else { } else {
return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider); return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player2, decider);
} }

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.40-SNAPSHOT</version> <version>1.6.40</version>
</parent> </parent>
<artifactId>forge-core</artifactId> <artifactId>forge-core</artifactId>

View File

@@ -121,6 +121,9 @@ public final class ImageKeys {
// if there's a 1st art variant try without it for .fullborder images // if there's a 1st art variant try without it for .fullborder images
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder")); file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
if (file != null) { return file; } if (file != null) { return file; }
// if there's a 1st art variant try with it for .fullborder images
file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder"));
if (file != null) { return file; }
// if there's an art variant try without it for .full images // if there's an art variant try without it for .full images
file = findFile(dir, filename.replaceAll("[0-9].full",".full")); file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
if (file != null) { return file; } if (file != null) { return file; }

View File

@@ -2,7 +2,6 @@ package forge;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -79,16 +78,30 @@ public class StaticData {
this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder))); this.customEditions = new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder)));
this.prefferedArt = prefferedArt; this.prefferedArt = prefferedArt;
lastInstance = this; lastInstance = this;
List<String> funnyCards = new ArrayList<>();
List<String> filtered = new ArrayList<>();
{ {
final Map<String, CardRules> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final Map<String, CardRules> regularCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final Map<String, CardRules> variantsCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final Map<String, CardRules> customizedCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); final Map<String, CardRules> customizedCards = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (CardEdition e : editions) {
if (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER) {
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
funnyCards.add(cis.name);
}
}
}
for (CardRules card : cardReader.loadCards()) { for (CardRules card : cardReader.loadCards()) {
if (null == card) continue; if (null == card) continue;
final String cardName = card.getName(); final String cardName = card.getName();
if (!loadNonLegalCards && !card.getType().isBasicLand() && funnyCards.contains(cardName))
filtered.add(cardName);
if (card.isVariant()) { if (card.isVariant()) {
variantsCards.put(cardName, card); variantsCards.put(cardName, card);
} else { } else {
@@ -104,15 +117,18 @@ public class StaticData {
} }
} }
if (!filtered.isEmpty()) {
Collections.sort(filtered);
}
commonCards = new CardDb(regularCards, editions); commonCards = new CardDb(regularCards, editions, filtered);
variantCards = new CardDb(variantsCards, editions); variantCards = new CardDb(variantsCards, editions, filtered);
customCards = new CardDb(customizedCards, customEditions); customCards = new CardDb(customizedCards, customEditions, filtered);
//must initialize after establish field values for the sake of card image logic //must initialize after establish field values for the sake of card image logic
commonCards.initialize(false, false, enableUnknownCards, loadNonLegalCards); commonCards.initialize(false, false, enableUnknownCards);
variantCards.initialize(false, false, enableUnknownCards, loadNonLegalCards); variantCards.initialize(false, false, enableUnknownCards);
customCards.initialize(false, false, enableUnknownCards, loadNonLegalCards); customCards.initialize(false, false, enableUnknownCards);
} }
{ {
@@ -125,14 +141,6 @@ public class StaticData {
} }
allTokens = new TokenDb(tokens, editions); allTokens = new TokenDb(tokens, editions);
} }
{
if (customCards.getAllCards().size() > 0) {
Collection<PaperCard> paperCards = customCards.getAllCards();
for(PaperCard p: paperCards)
commonCards.addCard(p);
}
}
} }
public static StaticData instance() { public static StaticData instance() {

View File

@@ -32,6 +32,7 @@ import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import forge.StaticData; import forge.StaticData;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -55,6 +56,8 @@ import forge.util.TextUtil;
public final class CardDb implements ICardDatabase, IDeckGenPool { public final class CardDb implements ICardDatabase, IDeckGenPool {
public final static String foilSuffix = "+"; public final static String foilSuffix = "+";
public final static char NameSetSeparator = '|'; public final static char NameSetSeparator = '|';
private final String exlcudedCardName = "Concentrate";
private final String exlcudedCardSet = "DS0";
// need this to obtain cardReference by name+set+artindex // need this to obtain cardReference by name+set+artindex
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), CollectionSuppliers.arrayLists()); private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), CollectionSuppliers.arrayLists());
@@ -66,8 +69,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, Integer> artIds = new HashMap<>(); private final Map<String, Integer> artIds = new HashMap<>();
private final Collection<PaperCard> roAllCards = Collections.unmodifiableCollection(allCardsByName.values());
private final CardEdition.Collection editions; private final CardEdition.Collection editions;
private List<String> filtered;
public enum SetPreference { public enum SetPreference {
Latest(false), Latest(false),
@@ -134,12 +137,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
} }
public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0) { public CardDb(Map<String, CardRules> rules, CardEdition.Collection editions0, List<String> filteredCards) {
this.filtered = filteredCards;
this.rulesByName = rules; this.rulesByName = rules;
this.editions = editions0; this.editions = editions0;
// create faces list from rules // create faces list from rules
for (final CardRules rule : rules.values() ) { for (final CardRules rule : rules.values() ) {
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
continue;
final ICardFace main = rule.getMainPart(); final ICardFace main = rule.getMainPart();
facesByName.put(main.getName(), main); facesByName.put(main.getName(), main);
if (main.getAltName() != null) { if (main.getAltName() != null) {
@@ -155,6 +161,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
} }
private ListMultimap<String, PaperCard> getAllCardsByName() {
return allCardsByName;
}
private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) { private void addSetCard(CardEdition e, CardInSet cis, CardRules cr) {
int artIdx = 1; int artIdx = 1;
String key = e.getCode() + "/" + cis.name; String key = e.getCode() + "/" + cis.name;
@@ -182,33 +192,24 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
reIndex(); reIndex();
} }
public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards, boolean loadNonLegalCards) { public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards) {
Set<String> allMissingCards = new LinkedHashSet<>(); Set<String> allMissingCards = new LinkedHashSet<>();
List<String> missingCards = new ArrayList<>(); List<String> missingCards = new ArrayList<>();
CardEdition upcomingSet = null; CardEdition upcomingSet = null;
Date today = new Date(); Date today = new Date();
List<String> skippedCardName = new ArrayList<>();
for (CardEdition e : editions.getOrderedEditions()) { for (CardEdition e : editions.getOrderedEditions()) {
boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION; boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION;
boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT; boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT;
//todo sets with nonlegal cards should have tags in them so we don't need to specify the code here
boolean skip = !loadNonLegalCards && (e.getType() == CardEdition.Type.FUNNY || e.getBorderColor() == CardEdition.BorderColor.SILVER);
if (logMissingPerEdition && isCoreExpSet) { if (logMissingPerEdition && isCoreExpSet) {
System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)"); System.out.print(e.getName() + " (" + e.getAllCardsInSet().size() + " cards)");
} }
if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) { if (coreOrExpSet && e.getDate().after(today) && upcomingSet == null) {
if (skip)
upcomingSet = e; upcomingSet = e;
} }
for (CardEdition.CardInSet cis : e.getAllCardsInSet()) { for (CardEdition.CardInSet cis : e.getAllCardsInSet()) {
CardRules cr = rulesByName.get(cis.name); CardRules cr = rulesByName.get(cis.name);
if (cr != null && !cr.getType().isBasicLand() && skip) {
skippedCardName.add(cis.name);
continue;
}
if (cr != null) { if (cr != null) {
addSetCard(e, cis, cr); addSetCard(e, cis, cr);
} }
@@ -244,7 +245,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
if (!contains(cr.getName())) { if (!contains(cr.getName())) {
if (upcomingSet != null) { if (upcomingSet != null) {
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1)); addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
} else if(enableUnknownCards && !skippedCardName.contains(cr.getName())) { } else if(enableUnknownCards) {
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. "); System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1)); addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
} }
@@ -255,6 +256,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
} }
public void addCard(PaperCard paperCard) { public void addCard(PaperCard paperCard) {
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
return;
allCardsByName.put(paperCard.getName(), paperCard); allCardsByName.put(paperCard.getName(), paperCard);
if (paperCard.getRules().getSplitType() == CardSplitType.None) { return; } if (paperCard.getRules().getSplitType() == CardSplitType.None) { return; }
@@ -268,11 +272,22 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
allCardsByName.put(paperCard.getRules().getMainPart().getName(), paperCard); allCardsByName.put(paperCard.getRules().getMainPart().getName(), paperCard);
} }
} }
private boolean excludeCard(String cardName, String cardEdition) {
if (filtered.isEmpty())
return false;
if (filtered.contains(cardName)) {
if (exlcudedCardSet.equalsIgnoreCase(cardEdition) && exlcudedCardName.equalsIgnoreCase(cardName))
return true;
else if (!exlcudedCardName.equalsIgnoreCase(cardName))
return true;
}
return false;
}
private void reIndex() { private void reIndex() {
uniqueCardsByName.clear(); uniqueCardsByName.clear();
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) { for (Entry<String, Collection<PaperCard>> kv : getAllCardsByName().asMap().entrySet()) {
uniqueCardsByName.put(kv.getKey(), getFirstWithImage(kv.getValue())); PaperCard pc = getFirstWithImage(kv.getValue());
uniqueCardsByName.put(kv.getKey(), pc);
} }
} }
@@ -561,6 +576,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return uniqueCardsByName.values(); return uniqueCardsByName.values();
} }
public Collection<PaperCard> getUniqueCardsNoAlt() {
return Maps.filterEntries(this.uniqueCardsByName, new Predicate<Entry<String, PaperCard>>() {
@Override
public boolean apply(Entry<String, PaperCard> e) {
if (e == null)
return false;
return e.getKey().equals(e.getValue().getName());
}
}).values();
}
public PaperCard getUniqueByName(final String name) { public PaperCard getUniqueByName(final String name) {
return uniqueCardsByName.get(getName(name)); return uniqueCardsByName.get(getName(name));
} }
@@ -575,11 +601,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override @Override
public Collection<PaperCard> getAllCards() { public Collection<PaperCard> getAllCards() {
return roAllCards; return Collections.unmodifiableCollection(getAllCardsByName().values());
}
public Collection<PaperCard> getAllCardsNoAlt() {
return Multimaps.filterEntries(getAllCardsByName(), new Predicate<Entry<String, PaperCard>>() {
@Override
public boolean apply(Entry<String, PaperCard> entry) {
return entry.getKey().equals(entry.getValue().getName());
}
}).values();
} }
public Collection<PaperCard> getAllNonPromoCards() { public Collection<PaperCard> getAllNonPromoCards() {
return Lists.newArrayList(Iterables.filter(this.roAllCards, new Predicate<PaperCard>() { return Lists.newArrayList(Iterables.filter(getAllCards(), new Predicate<PaperCard>() {
@Override @Override
public boolean apply(final PaperCard paperCard) { public boolean apply(final PaperCard paperCard) {
CardEdition edition = null; CardEdition edition = null;
@@ -602,13 +637,27 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override @Override
public List<PaperCard> getAllCards(String cardName) { public List<PaperCard> getAllCards(String cardName) {
return allCardsByName.get(getName(cardName)); return getAllCardsByName().get(getName(cardName));
}
public List<PaperCard> getAllCardsNoAlt(String cardName) {
return Lists.newArrayList(Multimaps.filterEntries(getAllCardsByName(), new Predicate<Entry<String, PaperCard>>() {
@Override
public boolean apply(Entry<String, PaperCard> entry) {
return entry.getKey().equals(entry.getValue().getName());
}
}).get(getName(cardName)));
} }
/** Returns a modifiable list of cards matching the given predicate */ /** Returns a modifiable list of cards matching the given predicate */
@Override @Override
public List<PaperCard> getAllCards(Predicate<PaperCard> predicate) { public List<PaperCard> getAllCards(Predicate<PaperCard> predicate) {
return Lists.newArrayList(Iterables.filter(this.roAllCards, predicate)); return Lists.newArrayList(Iterables.filter(getAllCards(), predicate));
}
/** Returns a modifiable list of cards matching the given predicate */
public List<PaperCard> getAllCardsNoAlt(Predicate<PaperCard> predicate) {
return Lists.newArrayList(Iterables.filter(getAllCardsNoAlt(), predicate));
} }
// Do I want a foiled version of these cards? // Do I want a foiled version of these cards?
@@ -630,12 +679,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
@Override @Override
public boolean contains(String name) { public boolean contains(String name) {
return allCardsByName.containsKey(getName(name)); return getAllCardsByName().containsKey(getName(name));
} }
@Override @Override
public Iterator<PaperCard> iterator() { public Iterator<PaperCard> iterator() {
return this.roAllCards.iterator(); return getAllCards().iterator();
} }
public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) { public Predicate<? super PaperCard> wasPrintedInSets(List<String> setCodes) {

View File

@@ -116,7 +116,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
} }
// reserved names of sections inside edition files, that are not parsed as cards // reserved names of sections inside edition files, that are not parsed as cards
private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens"); private static final List<String> reservedSectionNames = ImmutableList.of("metadata", "tokens", "other");
// commonly used printsheets with collector number // commonly used printsheets with collector number
public enum EditionSectionWithCollectorNumbers { public enum EditionSectionWithCollectorNumbers {
@@ -127,7 +127,8 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
EXTENDED_ART("extended art"), EXTENDED_ART("extended art"),
ALTERNATE_ART("alternate art"), ALTERNATE_ART("alternate art"),
BUY_A_BOX("buy a box"), BUY_A_BOX("buy a box"),
PROMO("promo"); PROMO("promo"),
BOX_TOPPER("box topper");
private final String name; private final String name;

View File

@@ -17,7 +17,7 @@
*/ */
package forge.card; package forge.card;
import java.util.StringTokenizer; import java.util.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -28,6 +28,9 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.util.TextUtil; import forge.util.TextUtil;
import static forge.card.MagicColor.Constant.*;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
/** /**
* A collection of methods containing full * A collection of methods containing full
* meta and gameplay properties of a card. * meta and gameplay properties of a card.
@@ -42,6 +45,7 @@ public final class CardRules implements ICardCharacteristics {
private ICardFace otherPart; private ICardFace otherPart;
private CardAiHints aiHints; private CardAiHints aiHints;
private ColorSet colorIdentity; private ColorSet colorIdentity;
private ColorSet deckbuildingColors;
private String meldWith; private String meldWith;
private String partnerWith; private String partnerWith;
@@ -581,4 +585,25 @@ public final class CardRules implements ICardCharacteristics {
} }
return null; return null;
} }
public ColorSet getDeckbuildingColors() {
if (deckbuildingColors == null) {
byte colors = 0;
if (mainPart.getType().isLand()) {
colors = getColorIdentity().getColor();
for (int i = 0; i < 5; i++) {
if (containsIgnoreCase(mainPart.getOracleText(), BASIC_LANDS.get(i))) {
colors |= 1 << i;
}
}
} else {
colors = getColor().getColor();
if (getOtherPart() != null) {
colors |= getOtherPart().getManaCost().getColorProfile();
}
}
deckbuildingColors = ColorSet.fromMask(colors);
}
return deckbuildingColors;
}
} }

View File

@@ -91,6 +91,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
public enum Supertype { public enum Supertype {
Basic, Basic,
Elite, Elite,
Host,
Legendary, Legendary,
Snow, Snow,
Ongoing, Ongoing,

View File

@@ -138,23 +138,11 @@ public final class MagicColor {
public static final ImmutableList<String> BASIC_LANDS = ImmutableList.of("Plains", "Island", "Swamp", "Mountain", "Forest"); public static final ImmutableList<String> BASIC_LANDS = ImmutableList.of("Plains", "Island", "Swamp", "Mountain", "Forest");
public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest"); public static final ImmutableList<String> SNOW_LANDS = ImmutableList.of("Snow-Covered Plains", "Snow-Covered Island", "Snow-Covered Swamp", "Snow-Covered Mountain", "Snow-Covered Forest");
public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>() public static final ImmutableMap<String, String> ANY_COLOR_CONVERSION = new ImmutableMap.Builder<String, String>()
.put("ManaColorConversion", "Additive") .put("ManaConversion", "AnyType->AnyColor")
.put("WhiteConversion", "Color")
.put("BlueConversion", "Color")
.put("BlackConversion", "Color")
.put("RedConversion", "Color")
.put("GreenConversion", "Color")
.put("ColorlessConversion", "Color")
.build(); .build();
public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>() public static final ImmutableMap<String, String> ANY_TYPE_CONVERSION = new ImmutableMap.Builder<String, String>()
.put("ManaColorConversion", "Additive") .put("ManaConversion", "AnyType->AnyType")
.put("WhiteConversion", "Type")
.put("BlueConversion", "Type")
.put("BlackConversion", "Type")
.put("RedConversion", "Type")
.put("GreenConversion", "Type")
.put("ColorlessConversion", "Type")
.build(); .build();
/** /**
* Private constructor to prevent instantiation. * Private constructor to prevent instantiation.

View File

@@ -31,7 +31,6 @@ public class PrintSheet {
for(CardEdition edition : editions) { for(CardEdition edition : editions) {
for(PrintSheet ps : edition.getPrintSheetsBySection()) { for(PrintSheet ps : edition.getPrintSheetsBySection()) {
System.out.println(ps.name);
sheets.add(ps.name, ps); sheets.add(ps.name, ps);
} }
} }

View File

@@ -59,6 +59,18 @@ public abstract class ManaAtom {
return 0; // generic return 0; // generic
} }
public static byte fromConversion(String s) {
switch (s) {
case "AnyColor": return ALL_MANA_COLORS;
case "AnyType": return ALL_MANA_TYPES;
}
byte b = 0;
for (char c : s.toCharArray()) {
b |= fromName(c);
}
return b;
}
public static int getIndexOfFirstManaType(final byte color){ public static int getIndexOfFirstManaType(final byte color){
for (int i = 0; i < MANATYPES.length; i++) { for (int i = 0; i < MANATYPES.length; i++) {
if ((color & MANATYPES[i]) != 0) { if ((color & MANATYPES[i]) != 0) {

View File

@@ -64,7 +64,10 @@ public enum ManaCostShard {
PR(ManaAtom.RED | ManaAtom.OR_2_LIFE, "P/R", "PR"), PR(ManaAtom.RED | ManaAtom.OR_2_LIFE, "P/R", "PR"),
PG(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "P/G", "PG"), PG(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "P/G", "PG"),
X(ManaAtom.IS_X, "X"); X(ManaAtom.IS_X, "X"),
// Colored only X, each color can be used to pay for this only once (for Emblazoned Golem)
COLORED_X(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.RED | ManaAtom.GREEN | ManaAtom.IS_X, "1");
private final int shard; private final int shard;

View File

@@ -489,15 +489,6 @@ public abstract class DeckGeneratorBase {
return dLands; return dLands;
} }
/**
* Get all dual lands that do not match this color combo.
*
* @return dual land names
*/
protected List<String> getInverseDualLandList() {
return inverseDLands;
}
private void addCardNameToList(String cardName, List<String> cardNameList) { private void addCardNameToList(String cardName, List<String> cardNameList) {
if (pool.contains(cardName)) { //avoid adding card if it's not in pool if (pool.contains(cardName)) { //avoid adding card if it's not in pool
cardNameList.add(cardName); cardNameList.add(cardName);

View File

@@ -113,8 +113,12 @@ public class CardTranslation {
public static void buildOracleMapping(String faceName, String oracleText) { public static void buildOracleMapping(String faceName, String oracleText) {
if (!needsTranslation() || oracleMappings.containsKey(faceName)) return; if (!needsTranslation() || oracleMappings.containsKey(faceName)) return;
String translatedName = getTranslatedName(faceName);
String translatedText = getTranslatedOracle(faceName); String translatedText = getTranslatedOracle(faceName);
if (translatedText.equals("")) {
// english card only, fall back
return;
}
String translatedName = getTranslatedName(faceName);
List <Pair <String, String> > mapping = new ArrayList<>(); List <Pair <String, String> > mapping = new ArrayList<>();
String [] splitOracleText = oracleText.split("\\\\n"); String [] splitOracleText = oracleText.split("\\\\n");
String [] splitTranslatedText = translatedText.split("\r\n\r\n"); String [] splitTranslatedText = translatedText.split("\r\n\r\n");

View File

@@ -20,6 +20,9 @@ public class ImageUtil {
key = key.substring(2); key = key.substring(2);
PaperCard cp = StaticData.instance().getCommonCards().getCard(key); PaperCard cp = StaticData.instance().getCommonCards().getCard(key);
if (cp == null) {
cp = StaticData.instance().getCustomCards().getCard(key);
}
if (cp == null) { if (cp == null) {
cp = StaticData.instance().getVariantCards().getCard(key); cp = StaticData.instance().getVariantCards().getCard(key);
} }
@@ -73,6 +76,8 @@ public class ImageUtil {
if (includeSet) { if (includeSet) {
String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition); String editionAliased = isDownloadUrl ? StaticData.instance().getEditions().getCode2ByCode(edition) : ImageKeys.getSetFolder(edition);
if (editionAliased == "") //FIXME: Custom Cards Workaround
editionAliased = edition;
return TextUtil.concatNoSpace(editionAliased, "/", fname); return TextUtil.concatNoSpace(editionAliased, "/", fname);
} else { } else {
return fname; return fname;

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<artifactId>forge</artifactId> <artifactId>forge</artifactId>
<groupId>forge</groupId> <groupId>forge</groupId>
<version>1.6.40-SNAPSHOT</version> <version>1.6.40</version>
</parent> </parent>
<artifactId>forge-game</artifactId> <artifactId>forge-game</artifactId>

View File

@@ -1,9 +1,9 @@
package forge.game; package forge.game;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -162,15 +162,23 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
* @return a boolean. * @return a boolean.
*/ */
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) { public boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
return matchesValid(o, valids, srcCard, srcCard.getController());
}
public boolean matchesValid(final Object o, final String[] valids, final Card srcCard, final Player srcPlayer) {
if (o instanceof GameObject) { if (o instanceof GameObject) {
final GameObject c = (GameObject) o; final GameObject c = (GameObject) o;
return c.isValid(valids, srcCard.getController(), srcCard, this); return c.isValid(valids, srcPlayer, srcCard, this);
} else if (o instanceof Iterable<?>) { } else if (o instanceof Iterable<?>) {
for (Object o2 : (Iterable<?>)o) { for (Object o2 : (Iterable<?>)o) {
if (matchesValid(o2, valids, srcCard)) { if (matchesValid(o2, valids, srcCard, srcPlayer)) {
return true; return true;
} }
} }
} else if (o instanceof String) {
if (ArrayUtils.contains(valids, o)) {
return true;
}
} }
return false; return false;
@@ -522,11 +530,16 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
@Override @Override
public Map<String, String> getSVars() { public Map<String, String> getSVars() {
Map<String, String> res = new HashMap<>(getSVarFallback().getSVars()); Map<String, String> res = Maps.newHashMap(getSVarFallback().getSVars());
res.putAll(sVars); res.putAll(sVars);
return res; return res;
} }
@Override
public Map<String, String> getDirectSVars() {
return sVars;
}
@Override @Override
public void setSVars(Map<String, String> newSVars) { public void setSVars(Map<String, String> newSVars) {
sVars = Maps.newTreeMap(); sVars = Maps.newTreeMap();

View File

@@ -2,10 +2,12 @@ package forge.game;
import forge.card.ColorSet; import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardState; import forge.game.card.CardState;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
@@ -189,6 +191,25 @@ public class ForgeScript {
if (!Expressions.compare(y, property, x)) { if (!Expressions.compare(y, property, x)) {
return false; return false;
} }
} else if (property.equals("ManaAbilityCantPaidFor")) {
SpellAbility paidFor = sourceController.getPaidForSA();
if (paidFor == null) {
return false;
}
ManaCostBeingPaid manaCost = paidFor.getManaCostBeingPaid();
// The following code is taken from InputPayMana.java, to determine if this mana ability can pay for SA currently being paid
byte colorCanUse = 0;
for (final byte color : ManaAtom.MANATYPES) {
if (manaCost.isAnyPartPayableWith(color, sourceController.getManaPool())) {
colorCanUse |= color;
}
}
if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, sourceController.getManaPool())) {
colorCanUse |= ManaAtom.GENERIC;
}
if (sa.isManaAbilityFor(paidFor, colorCanUse)) {
return false;
}
} else if (sa.getHostCard() != null) { } else if (sa.getHostCard() != null) {
return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility); return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility);
} }

View File

@@ -769,6 +769,13 @@ public class Game {
p.revealFaceDownCards(); p.revealFaceDownCards();
} }
for (Card c : cards) {
// CR 800.4d if card is controlled by opponent, LTB should trigger
if (c.getOwner().equals(p) && c.getController().equals(p)) {
c.getCurrentState().clearTriggers();
}
}
for (Card c : cards) { for (Card c : cards) {
if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) { if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) {
planarControllerLost = true; planarControllerLost = true;
@@ -782,8 +789,11 @@ public class Game {
cc.removeImprintedCard(c); cc.removeImprintedCard(c);
cc.removeEncodedCard(c); cc.removeEncodedCard(c);
cc.removeRemembered(c); cc.removeRemembered(c);
cc.removeAttachedTo(c);
} }
c.ceaseToExist(); getAction().ceaseToExist(c, false);
// CR 603.2f owner of trigger source lost game
triggerHandler.clearDelayedTrigger(c);
} else { } else {
// return stolen permanents // return stolen permanents
if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) { if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) {
@@ -822,6 +832,8 @@ public class Game {
// Remove leftover items from // Remove leftover items from
this.getStack().removeInstancesControlledBy(p); this.getStack().removeInstancesControlledBy(p);
getTriggerHandler().onPlayerLost(p);
ingamePlayers.remove(p); ingamePlayers.remove(p);
lostPlayers.add(p); lostPlayers.add(p);

View File

@@ -133,7 +133,7 @@ public class GameAction {
return c; return c;
} }
if (zoneFrom == null && !c.isToken()) { if (zoneFrom == null && !c.isToken()) {
zoneTo.add(c, position); zoneTo.add(c, position, CardUtil.getLKICopy(c));
checkStaticAbilities(); checkStaticAbilities();
game.getTriggerHandler().registerActiveTrigger(c, true); game.getTriggerHandler().registerActiveTrigger(c, true);
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo)); game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
@@ -251,6 +251,7 @@ public class GameAction {
copied.copyChangedTextFrom(c); copied.copyChangedTextFrom(c);
// copy exiled properties when adding to stack // copy exiled properties when adding to stack
// will be cleanup later in MagicStack
copied.setExiledWith(c.getExiledWith()); copied.setExiledWith(c.getExiledWith());
copied.setExiledBy(c.getExiledBy()); copied.setExiledBy(c.getExiledBy());
@@ -416,13 +417,7 @@ public class GameAction {
} }
if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) { if (!zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Stack)) {
Card with = c.getExiledWith(); c.cleanupExiledWith();
if (with != null) {
with.removeUntilLeavesBattlefield(c);
}
c.setExiledWith(null);
c.setExiledBy(null);
} }
} }
@@ -476,7 +471,7 @@ public class GameAction {
if (card == c) { if (card == c) {
zoneTo.add(copied, position, lastKnownInfo); // the modified state of the card is also reported here (e.g. for Morbid + Awaken) zoneTo.add(copied, position, lastKnownInfo); // the modified state of the card is also reported here (e.g. for Morbid + Awaken)
} else { } else {
zoneTo.add(card, position); zoneTo.add(card, position, CardUtil.getLKICopy(card));
} }
card.setZone(zoneTo); card.setZone(zoneTo);
} }
@@ -603,8 +598,6 @@ public class GameAction {
copied.setState(CardStateName.Original, true); copied.setState(CardStateName.Original, true);
} }
unattachCardLeavingBattlefield(copied); unattachCardLeavingBattlefield(copied);
// Remove all changed keywords
copied.removeAllChangedText(game.getNextTimestamp());
} else if (toBattlefield) { } else if (toBattlefield) {
// reset timestamp in changezone effects so they have same timestamp if ETB simutaneously // reset timestamp in changezone effects so they have same timestamp if ETB simutaneously
copied.setTimestamp(game.getNextTimestamp()); copied.setTimestamp(game.getNextTimestamp());
@@ -859,7 +852,24 @@ public class GameAction {
} }
public final Card exile(final Card c, SpellAbility cause) { public final Card exile(final Card c, SpellAbility cause) {
return exile(c, cause, null); if (c == null) {
return null;
}
return exile(new CardCollection(c), cause).get(0);
}
public final CardCollection exile(final CardCollection cards, SpellAbility cause) {
CardZoneTable table = new CardZoneTable();
CardCollection result = new CardCollection();
for (Card card : cards) {
if (cause != null) {
table.put(card.getZone().getZoneType(), ZoneType.Exile, card);
}
result.add(exile(card, cause, null));
}
if (cause != null) {
table.triggerChangesZoneAll(game, cause);
}
return result;
} }
public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) { public final Card exile(final Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
if (game.isCardExiled(c)) { if (game.isCardExiled(c)) {
@@ -909,6 +919,33 @@ public class GameAction {
} }
} }
public void ceaseToExist(Card c, boolean skipTrig) {
c.getZone().remove(c);
// CR 603.6c other players LTB triggers should work
if (!skipTrig) {
game.addChangeZoneLKIInfo(c);
CardCollectionView lastBattlefield = game.getLastStateBattlefield();
int idx = lastBattlefield.indexOf(c);
Card lki = null;
if (idx != -1) {
lki = lastBattlefield.get(idx);
}
if (lki == null) {
lki = CardUtil.getLKICopy(c);
}
if (game.getCombat() != null) {
game.getCombat().removeFromCombat(c);
game.getCombat().saveLKI(lki);
}
game.getTriggerHandler().registerActiveLTBTrigger(lki);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
runParams.put(AbilityKey.CardLKI, lki);
runParams.put(AbilityKey.Origin, c.getZone().getZoneType().name());
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, false);
}
}
// Temporarily disable (if mode = true) actively checking static abilities. // Temporarily disable (if mode = true) actively checking static abilities.
private void setHoldCheckingStaticAbilities(boolean mode) { private void setHoldCheckingStaticAbilities(boolean mode) {
holdCheckingStaticAbilities = mode; holdCheckingStaticAbilities = mode;
@@ -1245,6 +1282,7 @@ public class GameAction {
} }
if ((game.getRules().hasAppliedVariant(GameType.Commander) if ((game.getRules().hasAppliedVariant(GameType.Commander)
|| game.getRules().hasAppliedVariant(GameType.Brawl)
|| game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) { || game.getRules().hasAppliedVariant(GameType.Planeswalker)) && !checkAgain) {
Iterable<Card> cards = p.getCardsIn(ZoneType.Graveyard).threadSafeIterable(); Iterable<Card> cards = p.getCardsIn(ZoneType.Graveyard).threadSafeIterable();
for (final Card c : cards) { for (final Card c : cards) {
@@ -1266,7 +1304,7 @@ public class GameAction {
if (game.getCombat() != null) { if (game.getCombat() != null) {
game.getCombat().removeAbsentCombatants(); game.getCombat().removeAbsentCombatants();
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, null);
if (!checkAgain) { if (!checkAgain) {
break; // do not continue the loop break; // do not continue the loop
} }
@@ -1731,11 +1769,9 @@ public class GameAction {
} }
private void drawStartingHand(Player p1) { private void drawStartingHand(Player p1) {
//check initial hand //check initial hand
List<Card> lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable()); List<Card> lib1 = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
List<Card> hand1 = lib1.subList(0,p1.getMaxHandSize()); List<Card> hand1 = lib1.subList(0,p1.getMaxHandSize());
System.out.println(hand1.toString());
//shuffle //shuffle
List<Card> shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable()); List<Card> shuffledCards = Lists.newArrayList(p1.getZone(ZoneType.Library).getCards().threadSafeIterable());
@@ -1743,7 +1779,6 @@ public class GameAction {
//check a second hand //check a second hand
List<Card> hand2 = shuffledCards.subList(0,p1.getMaxHandSize()); List<Card> hand2 = shuffledCards.subList(0,p1.getMaxHandSize());
System.out.println(hand2.toString());
//choose better hand according to land count //choose better hand according to land count
float averageLandRatio = getLandRatio(lib1); float averageLandRatio = getLandRatio(lib1);

View File

@@ -41,14 +41,12 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.collect.FCollection;
public abstract class GameEntity extends GameObject implements IIdentifiable { public abstract class GameEntity extends GameObject implements IIdentifiable {
protected final int id; protected final int id;
private String name = ""; private String name = "";
private int preventNextDamage = 0; private int preventNextDamage = 0;
protected CardCollection attachedCards; protected CardCollection attachedCards = new CardCollection();
private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap(); private Map<Card, Map<String, String>> preventionShieldsWithEffects = Maps.newTreeMap();
protected Map<CounterType, Integer> counters = Maps.newHashMap(); protected Map<CounterType, Integer> counters = Maps.newHashMap();
@@ -285,43 +283,36 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
public abstract boolean hasKeyword(final Keyword keyword); public abstract boolean hasKeyword(final Keyword keyword);
public final CardCollectionView getEnchantedBy() { public final CardCollectionView getEnchantedBy() {
if (attachedCards == null) {
return CardCollection.EMPTY;
}
// enchanted means attached by Aura // enchanted means attached by Aura
return CardLists.filter(attachedCards, CardPredicates.Presets.AURA); return CardLists.filter(getAttachedCards(), CardPredicates.Presets.AURA);
} }
// doesn't include phased out cards
public final CardCollectionView getAttachedCards() { public final CardCollectionView getAttachedCards() {
return CardCollection.getView(attachedCards); return CardLists.filter(attachedCards, CardPredicates.phasedIn());
}
// for view does include phased out cards
public final CardCollectionView getAllAttachedCards() {
return attachedCards;
} }
public final void setAttachedCards(final Iterable<Card> cards) { public final void setAttachedCards(final Iterable<Card> cards) {
if (cards == null) {
attachedCards = null;
}
else {
attachedCards = new CardCollection(cards); attachedCards = new CardCollection(cards);
} updateAttachedCards();
getView().updateAttachedCards(this);
} }
public final boolean hasCardAttachments() { public final boolean hasCardAttachments() {
return FCollection.hasElements(attachedCards); return !getAttachedCards().isEmpty();
} }
public final boolean isEnchanted() { public final boolean isEnchanted() {
if (attachedCards == null) {
return false;
}
// enchanted means attached by Aura // enchanted means attached by Aura
return Iterables.any(attachedCards, CardPredicates.Presets.AURA); return Iterables.any(getAttachedCards(), CardPredicates.Presets.AURA);
} }
public final boolean hasCardAttachment(Card c) { public final boolean hasCardAttachment(Card c) {
return FCollection.hasElement(attachedCards, c); return getAttachedCards().contains(c);
} }
public final boolean isEnchantedBy(Card c) { public final boolean isEnchantedBy(Card c) {
// Rule 303.4k Even if c is no Aura it still counts // Rule 303.4k Even if c is no Aura it still counts
@@ -329,9 +320,6 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
} }
public final boolean hasCardAttachment(final String cardName) { public final boolean hasCardAttachment(final String cardName) {
if (attachedCards == null) {
return false;
}
return CardLists.count(getAttachedCards(), CardPredicates.nameEquals(cardName)) > 0; return CardLists.count(getAttachedCards(), CardPredicates.nameEquals(cardName)) > 0;
} }
public final boolean isEnchantedBy(final String cardName) { public final boolean isEnchantedBy(final String cardName) {
@@ -344,12 +332,8 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
* @param Card c * @param Card c
*/ */
public final void addAttachedCard(final Card c) { public final void addAttachedCard(final Card c) {
if (attachedCards == null) {
attachedCards = new CardCollection();
}
if (attachedCards.add(c)) { if (attachedCards.add(c)) {
getView().updateAttachedCards(this); updateAttachedCards();
getGame().fireEvent(new GameEventCardAttachment(c, null, this)); getGame().fireEvent(new GameEventCardAttachment(c, null, this));
} }
} }
@@ -359,17 +343,16 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
* @param Card c * @param Card c
*/ */
public final void removeAttachedCard(final Card c) { public final void removeAttachedCard(final Card c) {
if (attachedCards == null) { return; }
if (attachedCards.remove(c)) { if (attachedCards.remove(c)) {
if (attachedCards.isEmpty()) { updateAttachedCards();
attachedCards = null;
}
getView().updateAttachedCards(this);
getGame().fireEvent(new GameEventCardAttachment(c, this, null)); getGame().fireEvent(new GameEventCardAttachment(c, this, null));
} }
} }
public final void updateAttachedCards() {
getView().updateAttachedCards(this);
}
public final void unAttachAllCards() { public final void unAttachAllCards() {
for (Card c : Lists.newArrayList(getAttachedCards())) { for (Card c : Lists.newArrayList(getAttachedCards())) {
c.unattachFromEntity(this); c.unattachFromEntity(this);

View File

@@ -2,6 +2,9 @@ package forge.game;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import com.google.common.base.Optional;
import com.google.common.collect.ForwardingTable; import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@@ -10,49 +13,61 @@ import com.google.common.collect.Table;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterType, Integer> { public class GameEntityCounterTable extends ForwardingTable<Optional<Player>, GameEntity, Map<CounterType, Integer>> {
private Table<GameEntity, CounterType, Integer> dataMap = HashBasedTable.create(); private Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> dataMap = HashBasedTable.create();
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see com.google.common.collect.ForwardingTable#delegate() * @see com.google.common.collect.ForwardingTable#delegate()
*/ */
@Override @Override
protected Table<GameEntity, CounterType, Integer> delegate() { protected Table<Optional<Player>, GameEntity, Map<CounterType, Integer>> delegate() {
return dataMap; return dataMap;
} }
/* (non-Javadoc) public Integer put(Player putter, GameEntity object, CounterType type, Integer value) {
* @see com.google.common.collect.ForwardingTable#put(java.lang.Object, java.lang.Object, java.lang.Object) Optional<Player> o = Optional.fromNullable(putter);
*/ Map<CounterType, Integer> map = get(o, object);
@Override if (map == null) {
public Integer put(GameEntity rowKey, CounterType columnKey, Integer value) { map = Maps.newHashMap();
return super.put(rowKey, columnKey, get(rowKey, columnKey) + value); put(o, object, map);
}
return map.put(type, ObjectUtils.firstNonNull(map.get(type), 0) + value);
} }
public int get(Player putter, GameEntity object, CounterType type) {
@Override Optional<Player> o = Optional.fromNullable(putter);
public Integer get(Object rowKey, Object columnKey) { Map<CounterType, Integer> map = get(o, object);
if (!contains(rowKey, columnKey)) { if (map == null || !map.containsKey(type)) {
return 0; // helper to not return null value return 0;
} }
return super.get(rowKey, columnKey); return ObjectUtils.firstNonNull(map.get(type), 0);
} }
public int totalValues() {
int result = 0;
for (Map<CounterType, Integer> m : values()) {
for (Integer i : m.values()) {
result += i;
}
}
return result;
}
/* /*
* returns the counters that can still be removed from game entity * returns the counters that can still be removed from game entity
*/ */
public Map<CounterType, Integer> filterToRemove(GameEntity ge) { public Map<CounterType, Integer> filterToRemove(GameEntity ge) {
Map<CounterType, Integer> result = Maps.newHashMap(); Map<CounterType, Integer> result = Maps.newHashMap();
if (!containsRow(ge)) { if (!containsColumn(ge)) {
result.putAll(ge.getCounters()); result.putAll(ge.getCounters());
return result; return result;
} }
Map<CounterType, Integer> alreadyRemoved = row(ge); Map<CounterType, Integer> alreadyRemoved = column(ge).get(Optional.absent());
for (Map.Entry<CounterType, Integer> e : ge.getCounters().entrySet()) { for (Map.Entry<CounterType, Integer> e : ge.getCounters().entrySet()) {
Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0); Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0);
if (rest > 0) { if (rest > 0) {
@@ -65,19 +80,34 @@ public class GameEntityCounterTable extends ForwardingTable<GameEntity, CounterT
public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, CardTraitBase sa) { public Map<GameEntity, Integer> filterTable(CounterType type, String valid, Card host, CardTraitBase sa) {
Map<GameEntity, Integer> result = Maps.newHashMap(); Map<GameEntity, Integer> result = Maps.newHashMap();
for (Map.Entry<GameEntity, Integer> e : column(type).entrySet()) { for (Map.Entry<GameEntity, Map<Optional<Player>, Map<CounterType, Integer>>> gm : columnMap().entrySet()) {
if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) { if (gm.getKey().isValid(valid, host.getController(), host, sa)) {
result.put(e.getKey(), e.getValue()); for (Map<CounterType, Integer> cm : gm.getValue().values()) {
Integer old = ObjectUtils.firstNonNull(result.get(gm.getKey()), 0);
Integer v = ObjectUtils.firstNonNull(cm.get(type), 0);
result.put(gm.getKey(), old + v);
}
} }
} }
return result; return result;
} }
public void triggerCountersPutAll(final Game game) { public void triggerCountersPutAll(final Game game) {
if (!isEmpty()) { if (isEmpty()) {
return;
}
for (Cell<Optional<Player>, GameEntity, Map<CounterType, Integer>> c : cellSet()) {
if (c.getValue().isEmpty()) {
continue;
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Source, c.getRowKey().get());
runParams.put(AbilityKey.Object, c.getColumnKey());
runParams.put(AbilityKey.CounterMap, c.getValue());
game.getTriggerHandler().runTrigger(TriggerType.CounterPlayerAddedAll, runParams, false);
}
final Map<AbilityKey, Object> runParams = AbilityKey.newMap(); final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Objects, this); runParams.put(AbilityKey.Objects, this);
game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.CounterAddedAll, runParams, false);
} }
} }
}

View File

@@ -1,5 +1,6 @@
package forge.game; package forge.game;
import forge.game.card.CardCollectionView;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.trackable.TrackableObject; import forge.trackable.TrackableObject;
@@ -54,6 +55,12 @@ public abstract class GameEntityView extends TrackableObject {
public boolean hasCardAttachments() { public boolean hasCardAttachments() {
return getAttachedCards() != null; return getAttachedCards() != null;
} }
public Iterable<CardView> getAllAttachedCards() {
return get(TrackableProperty.AllAttachedCards);
}
public boolean hasAnyCardAttachments() {
return getAllAttachedCards() != null;
}
protected void updateAttachedCards(GameEntity e) { protected void updateAttachedCards(GameEntity e) {
if (e.hasCardAttachments()) { if (e.hasCardAttachments()) {
@@ -62,5 +69,11 @@ public abstract class GameEntityView extends TrackableObject {
else { else {
set(TrackableProperty.AttachedCards, null); set(TrackableProperty.AttachedCards, null);
} }
CardCollectionView all = e.getAllAttachedCards();
if (all.isEmpty()) {
set(TrackableProperty.AllAttachedCards, null);
} else {
set(TrackableProperty.AllAttachedCards, CardView.getCollection(all));
}
} }
} }

View File

@@ -134,7 +134,7 @@ public class GameFormat implements Comparable<GameFormat> {
if (!this.allowedSetCodes_ro.isEmpty()) { if (!this.allowedSetCodes_ro.isEmpty()) {
p = Predicates.and(p, printed ? p = Predicates.and(p, printed ?
IPaperCard.Predicates.printedInSets(this.allowedSetCodes_ro, printed) : IPaperCard.Predicates.printedInSets(this.allowedSetCodes_ro, printed) :
(Predicate<PaperCard>)(StaticData.instance().getCommonCards().wasPrintedInSets(this.allowedSetCodes_ro))); StaticData.instance().getCommonCards().wasPrintedInSets(this.allowedSetCodes_ro));
} }
if (!this.allowedRarities.isEmpty()) { if (!this.allowedRarities.isEmpty()) {
List<Predicate<? super PaperCard>> crp = Lists.newArrayList(); List<Predicate<? super PaperCard>> crp = Lists.newArrayList();
@@ -302,6 +302,7 @@ public class GameFormat implements Comparable<GameFormat> {
{ {
coreFormats.add("Standard.txt"); coreFormats.add("Standard.txt");
coreFormats.add("Pioneer.txt"); coreFormats.add("Pioneer.txt");
coreFormats.add("Historic.txt");
coreFormats.add("Modern.txt"); coreFormats.add("Modern.txt");
coreFormats.add("Legacy.txt"); coreFormats.add("Legacy.txt");
coreFormats.add("Vintage.txt"); coreFormats.add("Vintage.txt");
@@ -484,6 +485,10 @@ public class GameFormat implements Comparable<GameFormat> {
return this.map.get("Pioneer"); return this.map.get("Pioneer");
} }
public GameFormat getHistoric() {
return this.map.get("Historic");
}
public GameFormat getModern() { public GameFormat getModern() {
return this.map.get("Modern"); return this.map.get("Modern");
} }

View File

@@ -23,7 +23,7 @@ package forge.game;
public enum GlobalRuleChange { public enum GlobalRuleChange {
alwaysWither ("All damage is dealt as though its source had wither."), alwaysWither ("All damage is dealt as though its source had wither."),
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each turn."), attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat."),
manaBurn ("A player losing unspent mana causes that player to lose that much life."), manaBurn ("A player losing unspent mana causes that player to lose that much life."),
manapoolsDontEmpty ("Mana pools don't empty as steps and phases end."), manapoolsDontEmpty ("Mana pools don't empty as steps and phases end."),
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."), noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),

View File

@@ -15,6 +15,7 @@ public interface IHasSVars {
//public Set<String> getSVars(); //public Set<String> getSVars();
public Map<String, String> getSVars(); public Map<String, String> getSVars();
public Map<String, String> getDirectSVars();
public void removeSVar(final String var); public void removeSVar(final String var);
} }

View File

@@ -255,7 +255,7 @@ public class StaticEffect {
} }
// remove abilities // remove abilities
if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf") if (hasParam("AddAbility") || hasParam("GainsAbilitiesOf") || hasParam("GainsAbilitiesOfDefined")
|| hasParam("AddTrigger") || hasParam("AddStaticAbility") || hasParam("AddReplacementEffects") || hasParam("AddTrigger") || hasParam("AddStaticAbility") || hasParam("AddReplacementEffects")
|| hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) { || hasParam("RemoveAllAbilities") || hasParam("RemoveLandTypes")) {
affectedCard.removeChangedCardTraits(getTimestamp()); affectedCard.removeChangedCardTraits(getTimestamp());

View File

@@ -53,6 +53,7 @@ public final class AbilityFactory {
static final List<String> additionalAbilityKeys = Lists.newArrayList( static final List<String> additionalAbilityKeys = Lists.newArrayList(
"WinSubAbility", "OtherwiseSubAbility", // Clash "WinSubAbility", "OtherwiseSubAbility", // Clash
"BidSubAbility", // BidLifeEffect
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber "ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
"HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin "HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin
"TrueSubAbility", "FalseSubAbility", // Branch "TrueSubAbility", "FalseSubAbility", // Branch
@@ -272,7 +273,7 @@ public final class AbilityFactory {
for (final String key : additionalAbilityKeys) { for (final String key : additionalAbilityKeys) {
if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) { if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) {
spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), sVarHolder)); spellAbility.setAdditionalAbility(key, getAbility(state, mapParams.get(key), sVarHolder));
} }
} }

View File

@@ -33,12 +33,12 @@ public enum AbilityKey {
Cause("Cause"), Cause("Cause"),
Causer("Causer"), Causer("Causer"),
Championed("Championed"), Championed("Championed"),
CopySA("CopySA"),
Cost("Cost"), Cost("Cost"),
CostStack("CostStack"), CostStack("CostStack"),
CounterAmount("CounterAmount"), CounterAmount("CounterAmount"),
CounteredSA("CounteredSA"), CounteredSA("CounteredSA"),
CounterNum("CounterNum"), CounterNum("CounterNum"),
CounterMap("CounterMap"),
CounterTable("CounterTable"), CounterTable("CounterTable"),
CounterType("CounterType"), CounterType("CounterType"),
Crew("Crew"), Crew("Crew"),
@@ -76,6 +76,7 @@ public enum AbilityKey {
LifeGained("LifeGained"), LifeGained("LifeGained"),
Mana("Mana"), Mana("Mana"),
MergedCards("MergedCards"), MergedCards("MergedCards"),
Mode("Mode"),
MonstrosityAmount("MonstrosityAmount"), MonstrosityAmount("MonstrosityAmount"),
NewCard("NewCard"), NewCard("NewCard"),
NewCounterAmount("NewCounterAmount"), NewCounterAmount("NewCounterAmount"),
@@ -119,6 +120,7 @@ public enum AbilityKey {
Token("Token"), Token("Token"),
TokenNum("TokenNum"), TokenNum("TokenNum"),
Transformer("Transformer"), Transformer("Transformer"),
TriggeredParams("TriggeredParams"),
Vehicle("Vehicle"), Vehicle("Vehicle"),
Won("Won"); Won("Won");

View File

@@ -550,15 +550,21 @@ public class AbilityUtils {
players.remove(game.getPhaseHandler().getPlayerTurn()); players.remove(game.getPhaseHandler().getPlayerTurn());
val = CardFactoryUtil.playerXCount(players, calcX[1], card); val = CardFactoryUtil.playerXCount(players, calcX[1], card);
} }
else if (hType.startsWith("PropertyYou") && ability instanceof SpellAbility) { else if (hType.startsWith("PropertyYou")) {
if (ability instanceof SpellAbility) {
// Hollow One // Hollow One
players.add(((SpellAbility) ability).getActivatingPlayer()); players.add(((SpellAbility) ability).getActivatingPlayer());
} else {
players.add(player);
}
val = CardFactoryUtil.playerXCount(players, calcX[1], card); val = CardFactoryUtil.playerXCount(players, calcX[1], card);
} }
else if (hType.startsWith("Property") && ability instanceof SpellAbility) { else if (hType.startsWith("Property")) {
String defined = hType.split("Property")[1]; String defined = hType.split("Property")[1];
for (Player p : game.getPlayersInTurnOrder()) { for (Player p : game.getPlayersInTurnOrder()) {
if (p.hasProperty(defined, ((SpellAbility) ability).getActivatingPlayer(), ability.getHostCard(), ability)) { if (ability instanceof SpellAbility && p.hasProperty(defined, ((SpellAbility) ability).getActivatingPlayer(), ability.getHostCard(), ability)) {
players.add(p);
} else if (!(ability instanceof SpellAbility) && p.hasProperty(defined, player, ability.getHostCard(), ability)) {
players.add(p); players.add(p);
} }
} }
@@ -1395,6 +1401,14 @@ public class AbilityUtils {
} }
} }
// count times ability resolves this turn
if (!sa.isWrapper()) {
final Card host = sa.getHostCard();
if (host != null) {
host.addAbilityResolved(sa);
}
}
final ApiType api = sa.getApi(); final ApiType api = sa.getApi();
if (api == null) { if (api == null) {
sa.resolve(); sa.resolve();
@@ -1488,17 +1502,45 @@ public class AbilityUtils {
else if (unlessCost.equals("ChosenNumber")) { else if (unlessCost.equals("ChosenNumber")) {
cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true); cost = new Cost(new ManaCost(new ManaCostParser(String.valueOf(source.getChosenNumber()))), true);
} }
else if (unlessCost.equals("RememberedCostMinus2")) { else if (unlessCost.startsWith("DefinedCost")) {
Card rememberedCard = (Card) source.getFirstRemembered(); CardCollection definedCards = AbilityUtils.getDefinedCards(sa.getHostCard(), unlessCost.split("_")[1], sa);
if (rememberedCard == null) { if (definedCards.isEmpty()) {
sa.resolve(); sa.resolve();
resolveSubAbilities(sa, game); resolveSubAbilities(sa, game);
return; return;
} }
ManaCostBeingPaid newCost = new ManaCostBeingPaid(rememberedCard.getManaCost()); Card card = definedCards.getFirst();
newCost.decreaseGenericMana(2); ManaCostBeingPaid newCost = new ManaCostBeingPaid(card.getManaCost());
// Check if there's a third underscore for cost modifying
if (unlessCost.split("_").length == 3) {
String modifier = unlessCost.split("_")[2];
if (modifier.startsWith("Minus")) {
newCost.decreaseGenericMana(Integer.parseInt(modifier.substring(5)));
} else {
newCost.increaseGenericMana(Integer.parseInt(modifier.substring(4)));
}
}
cost = new Cost(newCost.toManaCost(), true); cost = new Cost(newCost.toManaCost(), true);
} }
else if (unlessCost.startsWith("DefinedSACost")) {
FCollection<SpellAbility> definedSAs = AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), unlessCost.split("_")[1], sa);
if (definedSAs.isEmpty()) {
sa.resolve();
resolveSubAbilities(sa, game);
return;
}
Card host = definedSAs.getFirst().getHostCard();
if (host.getManaCost() == null) {
cost = new Cost(ManaCost.ZERO, true);
} else {
int xCount = host.getManaCost().countX();
int xPaid = host.getXManaCostPaid() * xCount;
ManaCostBeingPaid toPay = new ManaCostBeingPaid(host.getManaCost());
toPay.decreaseShard(ManaCostShard.X, xCount);
toPay.increaseGenericMana(xPaid);
cost = new Cost(toPay.toManaCost(), true);
}
}
else if (!StringUtils.isBlank(sa.getSVar(unlessCost)) || !StringUtils.isBlank(source.getSVar(unlessCost))) { else if (!StringUtils.isBlank(sa.getSVar(unlessCost)) || !StringUtils.isBlank(source.getSVar(unlessCost))) {
// check for X costs (stored in SVars // check for X costs (stored in SVars
int xCost = calculateAmount(source, TextUtil.fastReplace(sa.getParam("UnlessCost"), int xCost = calculateAmount(source, TextUtil.fastReplace(sa.getParam("UnlessCost"),
@@ -1826,6 +1868,10 @@ public class AbilityUtils {
} }
return CardFactoryUtil.doXMath(v, expr, c); return CardFactoryUtil.doXMath(v, expr, c);
} }
if (sq[0].equals("ResolvedThisTurn")) {
return CardFactoryUtil.doXMath(sa.getResolvedThisTurn(), expr, c);
}
} }
if (l[0].startsWith("CountersAddedThisTurn")) { if (l[0].startsWith("CountersAddedThisTurn")) {
@@ -1834,35 +1880,37 @@ public class AbilityUtils {
return CardFactoryUtil.doXMath(game.getCounterAddedThisTurn(cType, parts[2], parts[3], c, player, ctb), expr, c); return CardFactoryUtil.doXMath(game.getCounterAddedThisTurn(cType, parts[2], parts[3], c, player, ctb), expr, c);
} }
// count valid cards in any specified zone/s
if (l[0].startsWith("Valid")) {
String[] lparts = l[0].split(" ", 2);
final String[] rest = lparts[1].split(",");
final CardCollectionView cardsInZones = lparts[0].length() > 5
? game.getCardsIn(ZoneType.listValueOf(lparts[0].substring(5)))
: game.getCardsIn(ZoneType.Battlefield);
CardCollection cards = CardLists.getValidCards(cardsInZones, rest, player, c, ctb);
return CardFactoryUtil.doXMath(cards.size(), expr, c);
}
} }
return CardFactoryUtil.xCount(c, s2); return CardFactoryUtil.xCount(c, s2);
} }
public static final void applyManaColorConversion(ManaConversionMatrix matrix, final Map<String, String> params) { public static final void applyManaColorConversion(ManaConversionMatrix matrix, final Map<String, String> params) {
String conversionType = params.get("ManaColorConversion"); String conversion = params.get("ManaConversion");
// Choices are Additives(OR) or Restrictive(AND) for (String pair : conversion.split(" ")) {
boolean additive = "Additive".equals(conversionType); // Check if conversion is additive or restrictive and how to split
boolean additive = pair.contains("->");
String[] sides = pair.split(additive ? "->" : "<-");
for(String c : MagicColor.Constant.COLORS_AND_COLORLESS) { if (sides[0].equals("AnyColor") || sides[0].equals("AnyType")) {
// Use the strings from MagicColor, since that's how the Script will be coming in as for (byte c : (sides[0].equals("AnyColor") ? MagicColor.WUBRG : MagicColor.WUBRGC)) {
String key = StringUtils.capitalize(c) + "Conversion"; matrix.adjustColorReplacement(c, ManaAtom.fromConversion(sides[1]), additive);
if (params.containsKey(key)) { }
String convertTo = params.get(key);
byte convertByte = 0;
if ("Type".equals(convertTo)) {
// IMPORTANT! We need to use Mana Color here not Card Color.
convertByte = ManaAtom.ALL_MANA_TYPES;
} else if ("Color".equals(convertTo)) {
// IMPORTANT! We need to use Mana Color here not Card Color.
convertByte = ManaAtom.ALL_MANA_COLORS;
} else { } else {
for (final String convertColor : convertTo.split(",")) { matrix.adjustColorReplacement(ManaAtom.fromConversion(sides[0]), ManaAtom.fromConversion(sides[1]), additive);
convertByte |= ManaAtom.fromName(convertColor);
}
}
// AdjustColorReplacement has two different matrices handling final mana conversion under the covers
matrix.adjustColorReplacement(ManaAtom.fromName(c), convertByte, additive);
} }
} }
} }
@@ -2010,19 +2058,6 @@ public class AbilityUtils {
} }
} }
public static SpellAbility getCause(SpellAbility sa) {
final SpellAbility root = sa.getRootAbility();
SpellAbility cause = sa;
if (root.isReplacementAbility()) {
SpellAbility replacingObject = (SpellAbility) root.getReplacingObject(AbilityKey.Cause);
if (replacingObject != null) {
cause = replacingObject;
}
}
return cause;
}
public static SpellAbility addSpliceEffects(final SpellAbility sa) { public static SpellAbility addSpliceEffects(final SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Player player = sa.getActivatingPlayer(); final Player player = sa.getActivatingPlayer();

View File

@@ -30,6 +30,7 @@ public enum ApiType {
Block (BlockEffect.class), Block (BlockEffect.class),
Bond (BondEffect.class), Bond (BondEffect.class),
Branch (BranchEffect.class), Branch (BranchEffect.class),
Camouflage (CamouflageEffect.class),
ChangeCombatants (ChangeCombatantsEffect.class), ChangeCombatants (ChangeCombatantsEffect.class),
ChangeTargets (ChangeTargetsEffect.class), ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class), ChangeText (ChangeTextEffect.class),
@@ -92,6 +93,7 @@ public enum ApiType {
Haunt (HauntEffect.class), Haunt (HauntEffect.class),
Investigate (InvestigateEffect.class), Investigate (InvestigateEffect.class),
ImmediateTrigger (ImmediateTriggerEffect.class), ImmediateTrigger (ImmediateTriggerEffect.class),
Learn (LearnEffect.class),
LookAt (LookAtEffect.class), LookAt (LookAtEffect.class),
LoseLife (LifeLoseEffect.class), LoseLife (LifeLoseEffect.class),
LosesGame (GameLossEffect.class), LosesGame (GameLossEffect.class),

View File

@@ -2,6 +2,7 @@ package forge.game.ability;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -19,6 +20,7 @@ import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.combat.Combat; import forge.game.combat.Combat;
@@ -634,10 +636,19 @@ public abstract class SpellAbilityEffect {
@Override @Override
public void run() { public void run() {
CardZoneTable untilTable = new CardZoneTable(); CardZoneTable untilTable = new CardZoneTable();
CardCollectionView untilCards = hostCard.getUntilLeavesBattlefield();
// if the list is empty, then the table doesn't need to be checked anymore
if (untilCards.isEmpty()) {
return;
}
for (Table.Cell<ZoneType, ZoneType, CardCollection> cell : triggerList.cellSet()) { for (Table.Cell<ZoneType, ZoneType, CardCollection> cell : triggerList.cellSet()) {
for (Card c : cell.getValue()) { for (Card c : cell.getValue()) {
// check if card is still in the until host leaves play list
if (!untilCards.contains(c)) {
continue;
}
// better check if card didn't changed zones again? // better check if card didn't changed zones again?
Card newCard = c.getZone().getCards().get(c); Card newCard = game.getCardState(c, null);
if (newCard == null || !newCard.equalsWithTimestamp(c)) { if (newCard == null || !newCard.equalsWithTimestamp(c)) {
continue; continue;
} }
@@ -646,9 +657,44 @@ public abstract class SpellAbilityEffect {
untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard); untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard);
} }
} }
untilTable.triggerChangesZoneAll(game); untilTable.triggerChangesZoneAll(game, null);
} }
}; };
} }
protected static void discard(SpellAbility sa, CardZoneTable table, Map<Player, CardCollectionView> discardedMap) {
Set<Player> discarders = discardedMap.keySet();
for (Player p : discarders) {
final CardCollection discardedByPlayer = new CardCollection();
for (Card card : Lists.newArrayList(discardedMap.get(p))) { // without copying will get concurrent modification exception
if (card == null) { continue; }
if (p.discard(card, sa, table) != null) {
discardedByPlayer.add(card);
if (sa.hasParam("RememberDiscarded")) {
sa.getHostCard().addRemembered(card);
}
}
}
discardedMap.put(p, discardedByPlayer);
}
for (Player p : discarders) {
CardCollectionView discardedByPlayer = discardedMap.get(p);
if (!discardedByPlayer.isEmpty()) {
boolean firstDiscard = p.getNumDiscardedThisTurn() - discardedByPlayer.size() == 0;
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Player, p);
runParams.put(AbilityKey.Cards, discardedByPlayer);
runParams.put(AbilityKey.Cause, sa);
runParams.put(AbilityKey.FirstTime, firstDiscard);
p.getGame().getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
if (sa.hasParam("RememberDiscardingPlayers")) {
sa.getHostCard().addRemembered(p);
}
}
}
}
} }

View File

@@ -74,7 +74,7 @@ public class AmassEffect extends TokenEffectBase {
makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged); makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
if (!useZoneTable) { if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game); triggerList.triggerChangesZoneAll(game, sa);
triggerList.clear(); triggerList.clear();
} }
game.fireEvent(new GameEventTokenCreated()); game.fireEvent(new GameEventTokenCreated());

View File

@@ -12,6 +12,8 @@ import forge.game.card.Card;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.TextUtil;
public class AnimateEffect extends AnimateEffectBase { public class AnimateEffect extends AnimateEffectBase {
@@ -139,6 +141,17 @@ public class AnimateEffect extends AnimateEffectBase {
List<Card> tgts = getCardsfromTargets(sa); List<Card> tgts = getCardsfromTargets(sa);
if (sa.hasParam("Optional")) {
final String targets = Lang.joinHomogenous(tgts);
final String message = sa.hasParam("OptionQuestion")
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
: getStackDescription(sa);
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
return;
}
}
for (final Card c : tgts) { for (final Card c : tgts) {
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc, doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
keywords, removeKeywords, hiddenKeywords, keywords, removeKeywords, hiddenKeywords,
@@ -229,9 +242,7 @@ public class AnimateEffect extends AnimateEffectBase {
final List<Card> tgts = getCardsfromTargets(sa); final List<Card> tgts = getCardsfromTargets(sa);
for (final Card c : tgts) { sb.append(Lang.joinHomogenous(tgts)).append(" ");
sb.append(c).append(" ");
}
// if power is -1, we'll assume it's not just setting toughness // if power is -1, we'll assume it's not just setting toughness
if (power != null && toughness != null) { if (power != null && toughness != null) {

View File

@@ -43,7 +43,7 @@ public class AttachEffect extends SpellAbilityEffect {
if (newZone != previousZone) { if (newZone != previousZone) {
table.put(previousZone, newZone, c); table.put(previousZone, newZone, c);
} }
table.triggerChangesZoneAll(game); table.triggerChangesZoneAll(game, sa);
} }
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();

Some files were not shown because too many files have changed in this diff Show More