Compare commits

..

136 Commits

Author SHA1 Message Date
Chris H
9bf567a966 Migrate SPM (and some other early spoilers) 2025-09-10 19:01:19 -04:00
Hans Mackowiak
a0be5e25cb CardEdition: fix tokenCN with ☇ 2025-09-10 22:10:00 +02:00
Hans Mackowiak
f2e3cdc111 fix c_1_1_myr to c_1_1_a_myr 2025-09-10 22:02:38 +02:00
Hans Mackowiak
7384aada40 Update pictures_of_spider_man.txt 2025-09-10 06:34:11 +02:00
Eradev
1a8a4f63ae Update Recruit Instructor 2025-09-10 06:31:01 +02:00
Eradev
530d1efcd8 Fix tokens and some images ID (#8684)
* Fig hags ID

* Double sided tokens should now be supported

* Fix zombie ID
2025-09-10 06:30:15 +02:00
tool4ever
d8b1d76f42 Update silk_web_weaver.txt 2025-09-09 21:40:24 +00:00
kevlahnota
830b145e88 Merge pull request #8680 from kevlahnota/master4
update android hwinfo
2025-09-09 22:52:21 +08:00
Anthony Calosa
51ed06d60a update android hwinfo 2025-09-09 22:36:09 +08:00
Eradev
ac9b73935f Tokens cleanup (F-G sets) (#8049)
* Update FEM

* Update PIP

* Update FRF

* Update 5DN

* Update 5ED

* Update FDN

* Update 4ED

* Update F12

* Update V15

* Update V14

* Update DRB

* Update V11

* Update V16

* Update V12

* Update V10

* Update V17

* Update FUT

* Update F17

* Update GDY

* Update GNT

* Update GN2

* Update GTC

* Update GS1

* Update PGPX

* Update GPT

* Update GK1

* Update GRN

* Update GK1

* Added old numbers back for now.

* Rename Fallout set
2025-09-09 13:35:19 +02:00
Eradev
fe2f49fbcc Tokens cleanup (D-E sets) (#8046)
* Update DKA

* Update DST

* Update DIS

* Update WHO

* Update DMR

* Update DMU

* Update DOM

* Update 2X2

* Update 2XM

* Update DGM

* Update DTK

* Update DS0

* Update DDQ

* Update DDC

* Update DDO

* Update DDU

* Update DDT

* Update DDS

* Update TD2

* Update DDN

* Update DDP

* Update DPA

* Update AFR

* Update DSC

* Update P8ED

* Update 8ED

* Update EMN

* Update EMA

* Update EVE

* Update EXO

* Update E02

* Fixed emblem name

* Add u_2_1_a_myr back

* Add b_0_0_germ back

* Update NPH
2025-09-09 13:24:40 +02:00
Eradev
3d31494a1b Tokens cleanup (N-P sets) (#8056)
* Update NMS

* Update NPH

* Update 9ED

* Update OGW

* Update ODY

* Update ONS

* Update PHEL

* Update OTJ

* Update ONE

* Update PLC

* Update PC2

* Re-add planes

* Update HOP

* Update PLS

* Update PCA

* Update PWCS

* Update PCY

* Update PD3
2025-09-09 13:15:57 +02:00
Eradev
ff42e730a2 Tokens cleanup (R-S sets) (#8138)
* Update SOK

* Update SCG

* Update ALA

* Update SOM

* Update SHM

* Update SOI

* Update SCD

* Update SNC

* Update STX

* Update STH

* Update PSVC

* Update RNA

* Update RAV

* Update RTR

* Update REV

* Update ROE

* Update PRIX

* Update RIX

* Fix RNA
2025-09-09 13:00:12 +02:00
Paul Hammerton
a66fb8791c Merge pull request #8678 from paulsnoops/sld-update
Edition updates: OM1, SLD
2025-09-09 10:49:43 +01:00
Paul Hammerton
2ad5d20e83 Edition updates: OM1, SLD 2025-09-09 10:45:32 +01:00
Hans Mackowiak
4790394698 Update miles_morales_ultimate_spider_man.txt
Fix typo
2025-09-09 08:42:16 +02:00
Eradev
8b1c427809 Fix crash when equipping 2025-09-09 05:42:46 +02:00
tool4ever
c0b667c373 Some clean up (#8673) 2025-09-08 20:40:03 +00:00
kevlahnota
ca61627f5b Merge pull request #8671 from kevlahnota/master4
restore backup save for errors
2025-09-08 19:28:52 +08:00
Anthony Calosa
c3e2a5b5ea update 2025-09-08 19:23:58 +08:00
Anthony Calosa
0d26b499d3 restore backup save for errors 2025-09-08 19:17:51 +08:00
kevlahnota
bbbc5e0ee6 Merge pull request #8669 from kevlahnota/master4
return extra info for save errors
2025-09-08 15:46:43 +08:00
Anthony Calosa
714d9442f1 return extra info for save errors 2025-09-08 15:41:16 +08:00
tool4ever
d199765e6d Update coal_hill_school.txt 2025-09-08 08:47:27 +02:00
kevlahnota
e5156d8999 Merge pull request #8668 from kevlahnota/master4
revert save read
2025-09-08 14:34:46 +08:00
Anthony Calosa
c0d5397541 revert save read
- todo find a better way to inform player with save error
2025-09-08 14:34:10 +08:00
kevlahnota
444897d0f9 Update lizard_connorss_curse.txt 2025-09-08 14:20:43 +08:00
kevlahnota
f2cb7956d3 Merge pull request #8667 from kevlahnota/master4
announce savefile error
2025-09-08 12:10:10 +08:00
Anthony Calosa
e0c2a49c6b don't write data with errors 2025-09-08 12:06:23 +08:00
Anthony Calosa
d64e5ebc13 announce savefile error 2025-09-08 12:01:32 +08:00
xanxer6rB
9bea1bc717 Update the_reaver_cleaver.txt (#8661) 2025-09-07 12:43:19 +00:00
Hans Mackowiak
bdf9573467 CantTarget: fix AffectedZone not for Players 2025-09-07 11:25:35 +02:00
Hans Mackowiak
042eb4bf79 CantTarget: return the StaticAbility 2025-09-07 10:35:05 +02:00
Hans Mackowiak
4b0337a836 remove unneeded Protection flag 2025-09-07 09:44:22 +02:00
kevlahnota
9284775921 update ImageView group, piles
should fix concurrentmodification and npe prevention
2025-09-07 13:54:52 +08:00
tool4ever
1671a3298e Fix AI sometimes tapping mana sources for 0 (#8659) 2025-09-06 14:18:36 +00:00
kevlahnota
64e3193a80 Merge pull request #8660 from kevlahnota/master4
update VSubmenuSealed panel
2025-09-06 21:35:57 +08:00
Anthony Calosa
3e6cd92f14 update VSubmenuSealed panel
- closes #8658
2025-09-06 21:32:17 +08:00
Paul Hammerton
b7d4e0129e Merge pull request #8657 from paulsnoops/edition-updates
Edition updates: MAR, OMB, PSPM, SPM
2025-09-06 10:48:18 +01:00
Paul Hammerton
f76127dcea Edition updates: MAR, OMB, PSPM, SPM 2025-09-06 10:33:25 +01:00
Paul Hammerton
c7c0938936 Merge pull request #8656 from paulsnoops/spm-formats
SPM Format updates (+ MAR, OM1, OMB, SPE)
2025-09-06 10:20:18 +01:00
Paul Hammerton
9788a815a7 SPM Format updates (+ MAR, OM1, OMB, SPE) 2025-09-06 10:17:33 +01:00
Matija Huremović
cce25a37c4 Added the -c (clock) flag to determine the maximum time in seconds before the sim execution calls the match a draw. (#8202) 2025-09-06 09:04:59 +00:00
Hans Mackowiak
85812d819f Mayhem: add Reminder for Land with no Cost (#8654)
* Mayhem: add Reminder for Land with no Cost
2025-09-06 11:02:15 +02:00
Hans Mackowiak
a974352908 CountersPutAi: prevent AI from going overflow 2025-09-06 09:18:42 +02:00
kevlahnota
45e04cfef7 Update CONTRIBUTORS.txt
reference:
https://api.github.com/repos/Card-Forge/forge/contributors
[?per_page=100&&page=1] | [?per_page=100&&page=2]
2025-09-06 12:00:22 +08:00
kevlahnota
6a83cb89f7 Merge pull request #8653 from kevlahnota/master4
update mobile BugReporter savetofile
2025-09-06 10:57:34 +08:00
Anthony Calosa
494bd75572 update mobile BugReporter savetofile 2025-09-06 10:53:34 +08:00
Paul Hammerton
b065e468b0 Merge pull request #8652 from paulsnoops/fix-spm
Edition updates: SPM
2025-09-05 18:49:52 +01:00
Paul Hammerton
86aff59e40 Edition updates: SPM 2025-09-05 18:49:16 +01:00
Paul Hammerton
748c4e1dd7 Merge pull request #8651 from paulsnoops/edition-updates
Edition updates: MAR, OM1, PSPM, SLD, SPM
2025-09-05 18:34:51 +01:00
Fulgur14
dfb776f526 Final SPM batch (#8648) 2025-09-05 17:33:46 +00:00
Paul Hammerton
ca5d2bb109 Undo Add OM1 AltNames 2025-09-05 18:33:40 +01:00
Paul Hammerton
b42050a40f Add OM1 AltNames 2025-09-05 18:30:37 +01:00
Paul Hammerton
14220ae37f Edition updates: PSPM & SPM tokens 2025-09-05 18:26:56 +01:00
Paul Hammerton
46900c3f49 Edition updates: MAR, OM1, SLD, SPM 2025-09-05 18:19:16 +01:00
tool4ever
cb2bf996d0 Fix Profane Transfusion (#8650)
* Param for Mister Negative
2025-09-05 17:08:46 +00:00
tool4ever
edbf6692f2 Remove fake KW (#8649) 2025-09-05 16:04:07 +00:00
tool4ever
b5f16b83a0 Fix AI checking cost before less expensive logic (#8643) 2025-09-05 09:54:53 +02:00
Fulgur14
ee4902a5ae Spider-Girl and friends (SPM) (#8644) 2025-09-05 09:54:05 +02:00
Fulgur14
0835817d8a Morbius and friends (SPM) (#8640) 2025-09-05 09:48:21 +02:00
Hans Mackowiak
15d4e1ac20 Update spider_suit.txt 2025-09-05 09:23:07 +02:00
kevlahnota
ecdce50f12 Merge pull request #8645 from kevlahnota/master4
NPE prevention
2025-09-05 10:50:45 +08:00
Anthony Calosa
247fd7810e NPE prevention 2025-09-05 06:43:48 +08:00
tool4ever
055cd40fb1 Fix special actions going through frozen stack (#8642) 2025-09-04 19:16:46 +00:00
Renato Filipe Vidal Santos
f1d448509e YEOE: 6 cards 2025-09-04 15:50:36 +00:00
Paul Hammerton
f2581855a4 Merge pull request #8639 from paulsnoops/master
Edition updates: MAR, SPM
2025-09-04 16:03:09 +01:00
Paul Hammerton
1e4c7d4b00 Update Marvel's Spider-Man.txt 2025-09-04 16:02:16 +01:00
Paul Hammerton
2d80e44f70 Update Marvel's Spider-Man.txt 2025-09-04 16:01:06 +01:00
Paul Hammerton
a207bdbb51 Edition updates 2025-09-04 15:54:26 +01:00
Jetz72
c09ec2f410 Merge pull request #8619 from Jetz72/fixes20250902
Fix Arachnus Spinner not choosing a target for the Web
2025-09-04 08:20:21 -05:00
kevlahnota
c579df4c82 Merge pull request #8584 from SapphiCat/master
Fix crash copying adventure decks with full slots
2025-09-04 18:35:09 +08:00
Fulgur14
d39ab505df Sandman's Quicksand (SPM) 2025-09-04 11:38:12 +02:00
Fulgur14
7756dfa5be Jackal and friends (SPM) (#8634) 2025-09-04 11:13:36 +02:00
kevlahnota
c1b6f3126e Merge pull request #8633 from Eradev/FixCollectorNoRegex
Fix CollNo pattern after #7921
2025-09-04 15:47:54 +08:00
kevlahnota
11d45e0fa3 Merge pull request #8636 from kevlahnota/master4
Prevent building some card vars for view purposes only
2025-09-04 14:09:21 +08:00
Anthony Calosa
0504b41dbd Prevent building some card vars for view purposes only
buggy cards will still crash ingame when invoked
2025-09-04 14:04:56 +08:00
kevlahnota
b670cc3736 Update spider_verse.txt 2025-09-04 13:19:43 +08:00
kevlahnota
885300ba49 Merge pull request #8635 from kevlahnota/master4
prevent Concurrent Modificaton onRefresh ItemListView
2025-09-04 13:07:38 +08:00
Anthony Calosa
ff8fd51bf1 prevent NPE, update The Clone Saga trigger 2025-09-04 12:58:11 +08:00
Anthony Calosa
80475c6b62 prevent Concurrent Modificaton onRefresh ItemListView 2025-09-04 12:47:06 +08:00
kevlahnota
88ea36523b Merge pull request #8632 from Eradev/RemoveSetLookupForJ21
Delete SetLookup for J21
2025-09-04 10:17:31 +08:00
Eradev
3386d79cb1 Remove the Alchemy cards that were removed. 2025-09-03 22:07:36 -04:00
Eradev
f97429d1d4 Fix CollNo pattern after #7921 2025-09-03 21:53:49 -04:00
kevlahnota
3628d4e22a Merge pull request #8630 from kevlahnota/master4
remove unsupported cards on game matches
2025-09-04 09:38:26 +08:00
Eradev
fffc9a4282 Delete SetLookup for J21 2025-09-03 21:09:57 -04:00
Anthony Calosa
7cb32b0b25 update deck check 2025-09-04 07:33:21 +08:00
Anthony Calosa
cb13682737 remove unsupported cards on game matches
- closes #8616
2025-09-04 06:50:04 +08:00
Fulgur14
9656a9fb4c Lizard, Connors's Curse (SPM) (#8629) 2025-09-03 20:55:26 +00:00
tool4ever
2fd9d3ae14 CardDb: Remove hardcoded workaround in favor of reusing newer filter (#8628) 2025-09-03 20:49:33 +00:00
Fulgur14
1ea90f6655 Black Cat and gang (SPM) (#8626) 2025-09-03 17:53:50 +00:00
Fulgur14
37342910fa Swarm, Being of Bees and friends (SPM) (#8627) 2025-09-03 17:46:08 +00:00
autumnmyst
3a00727602 Add new duplicate cards directory to deal with duplicate test cards and implement correct Fast // Furious card in UNK (#8520)
* Added Steamflogger Boss to eternal cards in UST
2025-09-03 17:39:53 +00:00
Fulgur14
589d592997 Rhino's Rampage (SPM) (#8624) 2025-09-03 16:12:49 +02:00
Jetz
83101f7f41 Fix incorrect conformity in adventure events. 2025-09-03 09:11:09 -04:00
Fulgur14
c7d24f1c01 4 SPM cards (#8623) 2025-09-03 13:06:42 +02:00
Fulgur14
2a678aa7e9 Spider-UK et al. (SPM) (#8613) 2025-09-03 13:04:46 +02:00
tool4ever
6a758ffc0a Mayhem for Lands (#8615) 2025-09-03 13:03:52 +02:00
tool4ever
83421bac56 Optional fixes (#8622) 2025-09-03 13:00:15 +02:00
Fulgur14
5234365cc1 Heroes' Hangout (SPM) (#8621) 2025-09-03 09:39:06 +02:00
Fulgur14
76558a841c Create the_spot_living_portal.txt (#8617) 2025-09-03 09:37:47 +02:00
Chris H
1338735000 Spiderman is only pick 2 if pod size is 4 2025-09-02 23:12:32 -04:00
Chris H
44c33e2955 Apply pod size to Desktop UI draft view 2025-09-02 23:12:32 -04:00
Chris H
b8a5668db6 Add ability to draft with less than 8 players 2025-09-02 23:12:32 -04:00
Chris H
6a84a7a6f0 Update token entries in Fate Reforged.txt 2025-09-02 23:12:14 -04:00
Jetz
4811c9d726 end of file line break 2025-09-02 20:08:09 -04:00
Jetz
7cea6fce1e Fix Arachnus Spinner not choosing a target for the Web 2025-09-02 20:06:07 -04:00
tool4ever
46ba289329 Update sculpted_sunburst.txt
Closes #8618
2025-09-02 21:52:24 +00:00
Fulgur14
2c9e5ee814 Superior Spider-Man and other nonsense (SPM) (#8614) 2025-09-02 19:09:54 +00:00
Eradev
230eba687d Add unique suffix for reversible cards (#7921) 2025-09-02 18:05:19 +00:00
Fulgur14
5faf317aaf 3 SPM cards (2nd September) (#8612) 2025-09-02 16:59:23 +02:00
Fulgur14
388448c94f Cosmic Spider-Man (SPM) (#8610) 2025-09-02 16:24:11 +02:00
Fulgur14
230282b525 Parker Luck (SPM) (#8609) 2025-09-02 15:34:22 +02:00
Fulgur14
0f92bc18d4 Chameleon, Master of Disguise & Spinneret and Spiderling (#8608) 2025-09-02 13:16:27 +02:00
Paul Hammerton
dc4cee62dc Merge pull request #8607 from paulsnoops/edition-updates
Edition updates: MAR, SPM
2025-09-02 10:44:36 +01:00
Paul Hammerton
ea4c8fbc72 Edition updates: MAR, SPM 2025-09-02 10:42:42 +01:00
Fulgur14
068ea955d9 Impostor Syndrome (SPM) (#8600) 2025-09-02 09:19:18 +02:00
xanxer6rB
f898263bac Update the_soul_stone.txt
Added missing T: Add {B}
2025-09-02 06:34:47 +02:00
Hans Mackowiak
a3147f5e26 ~ fix spiders_man_heroic_horde TrigGainLife 2025-09-02 00:43:53 +02:00
tool4ever
a309eaee02 Update ultimate_green_goblin.txt 2025-09-01 19:25:29 +00:00
Fulgur14
94b058ec07 Agent Venom (SPM) (#8599) 2025-09-01 18:02:11 +00:00
Fulgur14
46bf3348be Flash Thompson and Peter Parker's Camera (SPM) (#8598) 2025-09-01 17:27:24 +00:00
Fulgur14
7c4c820e92 Ezekiel Sims and friends (SPM) (#8596) 2025-09-01 16:35:03 +00:00
Fulgur14
ed3ea99e1a Spiders-Man, Heroic Horde (SPM) (#8593) 2025-09-01 16:24:07 +02:00
tool4ever
ec21a0c789 The Soul Stone and support (#8579) 2025-09-01 13:45:01 +02:00
Fulgur14
bfe0a78765 Radioactive Spider (SPM) (#8592) 2025-09-01 13:40:06 +02:00
Fulgur14
0abc224e1b Steel Wrecking Ball (SPM) (#8588) 2025-08-31 18:06:16 +00:00
SapphiCat
fa3ce1c7f7 Merge branch 'Card-Forge:master' into master 2025-08-31 07:35:08 -07:00
tool4ever
48a5046d93 Fix Urza's Miter (#8586) 2025-08-31 10:39:59 +00:00
Fulgur14
9f9e68f34d Create pictures_of_spider_man.txt (#8583) 2025-08-31 07:29:35 +00:00
kit
d7c7aaa51e Fix crash copying adventure decks with full slots 2025-08-30 14:02:02 -07:00
tool4ever
ffe883e7d9 Behold the Sinister Six! and support (#8578) 2025-08-30 10:57:13 +00:00
Hans Mackowiak
b07a9944aa GameActionUtil: fix getFirstSpellAbility using castSA 2025-08-30 12:55:28 +02:00
Fulgur14
997d489a7e SPM cards from the debut (#8576) 2025-08-30 10:19:58 +00:00
Paul Hammerton
d181746679 Merge pull request #8581 from paulsnoops/master
Edition updates: MAR, PF25, SLD, SPM
2025-08-30 11:16:07 +01:00
Paul Hammerton
693d6cd9e3 Edition updates: MAR, PF25, SLD, SPM 2025-08-30 11:14:15 +01:00
610 changed files with 4168 additions and 1218 deletions

View File

@@ -15,7 +15,7 @@ public class Main {
public static void main(String[] args) {
GuiBase.setInterface(new GuiMobile(Files.exists(Paths.get("./res"))?"./":"../forge-gui/"));
GuiBase.setDeviceInfo(null, 0, 0);
GuiBase.setDeviceInfo(null, 0, 0, System.getProperty("user.home") + "/Downloads/");
new EditorMainWindow(Config.instance());
}
}

View File

@@ -974,17 +974,13 @@ public class ComputerUtilCombat {
continue;
}
int pBonus = 0;
if (ability.getApi() == ApiType.Pump) {
if (!ability.hasParam("NumAtt")) {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
if (pBonus > 0) {
power += pBonus;
}
}
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
} else if (ability.getApi() == ApiType.PutCounter) {
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
continue;
@@ -998,12 +994,11 @@ public class ComputerUtilCombat {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
if (pBonus > 0) {
power += pBonus;
}
}
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
}
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
power += pBonus;
}
}
@@ -1107,17 +1102,13 @@ public class ComputerUtilCombat {
continue;
}
int tBonus = 0;
if (ability.getApi() == ApiType.Pump) {
if (!ability.hasParam("NumDef")) {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
if (tBonus > 0) {
toughness += tBonus;
}
}
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
} else if (ability.getApi() == ApiType.PutCounter) {
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
continue;
@@ -1131,12 +1122,11 @@ public class ComputerUtilCombat {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
if (tBonus > 0) {
toughness += tBonus;
}
}
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
}
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
toughness += tBonus;
}
}
return toughness;
@@ -1305,6 +1295,7 @@ public class ComputerUtilCombat {
continue;
}
int pBonus = 0;
if (ability.getApi() == ApiType.Pump) {
if (!ability.hasParam("NumAtt")) {
continue;
@@ -1314,11 +1305,8 @@ public class ComputerUtilCombat {
continue;
}
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
if (pBonus > 0) {
power += pBonus;
}
if (!ability.getPayCosts().hasTapCost()) {
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
}
} else if (ability.getApi() == ApiType.PutCounter) {
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
@@ -1333,13 +1321,14 @@ public class ComputerUtilCombat {
continue;
}
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
if (pBonus > 0) {
power += pBonus;
}
if (!ability.getPayCosts().hasTapCost()) {
pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
}
}
if (pBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
power += pBonus;
}
}
return power;
}
@@ -1530,16 +1519,14 @@ public class ComputerUtilCombat {
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
continue;
}
if (!ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
continue;
}
int tBonus = 0;
if (ability.getApi() == ApiType.Pump) {
if (!ability.hasParam("NumDef")) {
continue;
}
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
} else if (ability.getApi() == ApiType.PutCounter) {
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
continue;
@@ -1553,10 +1540,11 @@ public class ComputerUtilCombat {
continue;
}
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
if (tBonus > 0) {
toughness += tBonus;
}
tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParamOrDefault("CounterNum", "1"), ability);
}
if (tBonus > 0 && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
toughness += tBonus;
}
}
return toughness;

View File

@@ -291,6 +291,12 @@ public class ComputerUtilMana {
continue;
}
int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1;
if (amount <= 0) {
// wrong gamestate for variable amount
continue;
}
if (sa.getApi() == ApiType.Animate) {
// For abilities like Genju of the Cedars, make sure that we're not activating the aura ability by tapping the enchanted card for mana
if (saHost.isAura() && "Enchanted".equals(sa.getParam("Defined"))
@@ -443,7 +449,6 @@ public class ComputerUtilMana {
manaProduced = manaProduced.replace(s, color);
}
} else if (saMana.hasParam("ReplaceColor")) {
// replace color
String color = saMana.getParam("ReplaceColor");
if ("Chosen".equals(color)) {
if (card.hasChosenColor()) {
@@ -735,7 +740,8 @@ public class ComputerUtilMana {
if (saPayment != null && ComputerUtilCost.isSacrificeSelfCost(saPayment.getPayCosts())) {
if (sa.getTargets() != null && sa.getTargets().contains(saPayment.getHostCard())) {
saExcludeList.add(saPayment); // not a good idea to sac a card that you're targeting with the SA you're paying for
// not a good idea to sac a card that you're targeting with the SA you're paying for
saExcludeList.add(saPayment);
continue;
}
}
@@ -1496,7 +1502,7 @@ public class ComputerUtilMana {
}
if (!cost.isReusuableResource()) {
for(CostPart part : cost.getCostParts()) {
for (CostPart part : cost.getCostParts()) {
if (part instanceof CostSacrifice && !part.payCostFromSource()) {
unpreferredCost = true;
}
@@ -1587,10 +1593,8 @@ public class ComputerUtilMana {
// don't use abilities with dangerous drawbacks
AbilitySub sub = m.getSubAbility();
if (sub != null) {
if (!SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
continue;
}
if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) {
continue;
}
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list

View File

@@ -1347,6 +1347,11 @@ public class PlayerControllerAi extends PlayerController {
// Ai won't understand that anyway
}
@Override
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
// Ai won't understand that anyway
}
@Override
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) {
// TODO check if profile detection set to Auto

View File

@@ -442,11 +442,9 @@ public class CountersPutAi extends CountersAi {
}
sa.addDividedAllocation(c, amount);
return decision;
} else {
if (!hasSacCost) {
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
return decision;
}
} else if (!hasSacCost) {
// for Sacrifice costs, evaluate further to see if it's worth using the ability before the card dies
return decision;
}
}
}
@@ -609,6 +607,20 @@ public class CountersPutAi extends CountersAi {
}
final int currCounters = cards.get(0).getCounters(CounterType.getType(type));
// adding counters would cause counter amount to overflow
if (Integer.MAX_VALUE - currCounters <= amount) {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
if (type.equals("P1P1")) {
if (Integer.MAX_VALUE - cards.get(0).getNetPower() <= amount) {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
if (Integer.MAX_VALUE - cards.get(0).getNetToughness() <= amount) {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
}
// each non +1/+1 counter on the card is a 10% chance of not
// activating this ability.
@@ -670,14 +682,12 @@ public class CountersPutAi extends CountersAi {
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
if (sa.usesTargeting()) {
CardCollection list = null;
CardCollection list;
if (sa.isCurse()) {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
} else {
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
}
list = CardLists.getTargetableCards(list, sa);
if (list.isEmpty() && isMandatoryTrigger) {
@@ -693,9 +703,8 @@ public class CountersPutAi extends CountersAi {
|| sa.getTargets().isEmpty()) {
sa.resetTargets();
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
} else {
break;
}
break;
}
if (sa.isCurse()) {
@@ -738,8 +747,6 @@ public class CountersPutAi extends CountersAi {
final SpellAbility root = sa.getRootAbility();
final Card source = sa.getHostCard();
final String aiLogic = sa.getParamOrDefault("AILogic", "");
boolean preferred = true;
CardCollection list;
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final boolean divided = sa.isDividedAsYouChoose();
final int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
@@ -761,11 +768,11 @@ public class CountersPutAi extends CountersAi {
AiAbilityDecision decision = doChargeToCMCLogic(ai, sa);
if (decision.willingToPlay()) {
return decision;
} else if (mandatory) {
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
} else {
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
if (mandatory) {
return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay);
}
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
if (!sa.usesTargeting()) {
@@ -816,19 +823,19 @@ public class CountersPutAi extends CountersAi {
}
}
if (sa.isCurse()) {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
} else {
list = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
}
list = CardLists.getTargetableCards(list, sa);
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
int totalTargets = list.size();
sa.resetTargets();
Iterable<Card> filteredField;
if (sa.isCurse()) {
filteredField = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
} else {
filteredField = ai.getCardsIn(ZoneType.Battlefield);
}
CardCollection list = CardLists.getTargetableCards(filteredField, sa);
list = ComputerUtil.filterAITgts(sa, ai, list, false);
int totalTargets = list.size();
boolean preferred = true;
while (sa.canAddMoreTarget()) {
if (mandatory) {
// When things are mandatory, gotta handle a little differently
@@ -865,27 +872,21 @@ public class CountersPutAi extends CountersAi {
if (choice == null && mandatory) {
choice = Aggregates.random(list);
}
} else if (type.equals("M1M1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
if (type.equals("M1M1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
choice = Aggregates.random(list);
}
} else if (preferred) {
list = ComputerUtil.getSafeTargets(ai, sa, list);
choice = chooseBoonTarget(list, type);
if (choice == null && mandatory) {
choice = Aggregates.random(list);
}
} else if (type.equals("P1P1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
if (preferred) {
list = ComputerUtil.getSafeTargets(ai, sa, list);
choice = chooseBoonTarget(list, type);
if (choice == null && mandatory) {
choice = Aggregates.random(list);
}
} else {
if (type.equals("P1P1")) {
choice = ComputerUtilCard.getWorstCreatureAI(list);
} else {
choice = Aggregates.random(list);
}
}
choice = Aggregates.random(list);
}
if (choice != null && divided) {
int alloc = Math.max(amount / totalTargets, 1);
@@ -1080,8 +1081,7 @@ public class CountersPutAi extends CountersAi {
Player ai = sa.getActivatingPlayer();
GameEntity e = (GameEntity) params.get("Target");
// for Card try to select not useless counter
if (e instanceof Card) {
Card c = (Card) e;
if (e instanceof Card c) {
if (c.getController().isOpponentOf(ai)) {
if (options.contains(CounterEnumType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
return CounterEnumType.M1M1;
@@ -1098,8 +1098,7 @@ public class CountersPutAi extends CountersAi {
}
}
}
} else if (e instanceof Player) {
Player p = (Player) e;
} else if (e instanceof Player p) {
if (p.isOpponentOf(ai)) {
if (options.contains(CounterEnumType.POISON)) {
return CounterEnumType.POISON;
@@ -1233,9 +1232,8 @@ public class CountersPutAi extends CountersAi {
if (numCtrs < optimalCMC) {
// If the AI has less counters than the optimal CMC, it should play the ability.
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
} else {
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
// If the AI has enough counters or more than the optimal CMC, it should not play the ability.
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
}
}

View File

@@ -78,7 +78,7 @@ public class PermanentCreatureAi extends PermanentAi {
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& ai.getManaPool().totalMana() <= 0
&& (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS))
&& (!card.hasETBTrigger(true) && !card.hasSVar("AmbushAI"))
&& !card.hasETBTrigger(true) && !card.hasSVar("AmbushAI")
&& game.getStack().isEmpty()
&& !ComputerUtil.castPermanentInMain1(ai, sa)) {
// AiPlayDecision.AnotherTime;

View File

@@ -31,6 +31,8 @@ public final class ImageKeys {
public static final String MONARCH_IMAGE = "monarch";
public static final String THE_RING_IMAGE = "the_ring";
public static final String RADIATION_IMAGE = "radiation";
public static final String SPEED_IMAGE = "speed";
public static final String MAX_SPEED_IMAGE = "max_speed";
public static final String BACKFACE_POSTFIX = "$alt";
public static final String SPECFACE_W = "$wspec";

View File

@@ -45,8 +45,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
public final static char NameSetSeparator = '|';
public final static String FlagPrefix = "#";
public static final String FlagSeparator = "\t";
private final String exlcudedCardName = "Concentrate";
private final String exlcudedCardSet = "DS0";
// need this to obtain cardReference by name+set+artindex
private final ListMultimap<String, PaperCard> allCardsByName = Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), Lists::newArrayList);
@@ -303,7 +301,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// create faces list from rules
for (final CardRules rule : rules.values()) {
if (filteredCards.contains(rule.getName()) && !exlcudedCardName.equalsIgnoreCase(rule.getName()))
if (filteredCards.contains(rule.getName()))
continue;
for (ICardFace face : rule.getAllFaces()) {
addFaceToDbNames(face);
@@ -501,8 +499,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
public void addCard(PaperCard paperCard) {
if (excludeCard(paperCard.getName(), paperCard.getEdition()))
if (filtered.contains(paperCard.getName())) {
return;
}
allCardsByName.put(paperCard.getName(), paperCard);
@@ -523,17 +522,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
}
}
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 return !exlcudedCardName.equalsIgnoreCase(cardName);
}
return false;
}
private void reIndex() {
uniqueCardsByName.clear();
for (Entry<String, Collection<PaperCard>> kv : allCardsByName.asMap().entrySet()) {

View File

@@ -52,6 +52,14 @@ import java.util.stream.Collectors;
*/
public final class CardEdition implements Comparable<CardEdition> {
public DraftOptions getDraftOptions() {
return draftOptions;
}
public void setDraftOptions(DraftOptions draftOptions) {
this.draftOptions = draftOptions;
}
// immutable
public enum Type {
UNKNOWN,
@@ -275,18 +283,22 @@ public final class CardEdition implements Comparable<CardEdition> {
// Booster/draft info
private List<BoosterSlot> boosterSlots = null;
private boolean smallSetOverride = false;
private boolean foilAlwaysInCommonSlot = false;
private String additionalUnlockSet = "";
private FoilType foilType = FoilType.NOT_SUPPORTED;
// Replace all of these things with booster slots
private boolean foilAlwaysInCommonSlot = false;
private double foilChanceInBooster = 0;
private double chanceReplaceCommonWith = 0;
private String slotReplaceCommonWith = "Common";
private String additionalSheetForFoils = "";
private String additionalUnlockSet = "";
private String boosterMustContain = "";
private String boosterReplaceSlotFromPrintSheet = "";
private String sheetReplaceCardFromSheet = "";
private String sheetReplaceCardFromSheet2 = "";
private String doublePickDuringDraft = "";
// Draft options
private DraftOptions draftOptions = null;
private String[] chaosDraftThemes = new String[0];
private final ListMultimap<String, EditionEntry> cardMap;
@@ -373,7 +385,6 @@ public final class CardEdition implements Comparable<CardEdition> {
public String getSlotReplaceCommonWith() { return slotReplaceCommonWith; }
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
public String getDoublePickDuringDraft() { return doublePickDuringDraft; }
public String getBoosterMustContain() { return boosterMustContain; }
public String getBoosterReplaceSlotFromPrintSheet() { return boosterReplaceSlotFromPrintSheet; }
public String getSheetReplaceCardFromSheet() { return sheetReplaceCardFromSheet; }
@@ -619,7 +630,7 @@ public final class CardEdition implements Comparable<CardEdition> {
* functional variant name - grouping #9
*/
// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$"
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
"(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$"
);
final Pattern tokenPattern = Pattern.compile(
@@ -628,7 +639,7 @@ public final class CardEdition implements Comparable<CardEdition> {
* name - grouping #3
* artist name - grouping #5
*/
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
"(^(.?[0-9A-Z-]+\\S?[A-Z]*)\\s)?([^@]*)( @(.*))?$"
);
ListMultimap<String, EditionEntry> cardMap = ArrayListMultimap.create();
@@ -808,7 +819,6 @@ public final class CardEdition implements Comparable<CardEdition> {
res.additionalUnlockSet = metadata.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
res.smallSetOverride = metadata.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
res.doublePickDuringDraft = metadata.get("DoublePick", ""); // "FirstPick" or "Always"
res.boosterMustContain = metadata.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
res.boosterReplaceSlotFromPrintSheet = metadata.get("BoosterReplaceSlotFromPrintSheet", ""); // e.g. Zendikar Rising guaranteed double-faced card
@@ -816,6 +826,23 @@ public final class CardEdition implements Comparable<CardEdition> {
res.sheetReplaceCardFromSheet2 = metadata.get("SheetReplaceCardFromSheet2", "");
res.chaosDraftThemes = metadata.get("ChaosDraftThemes", "").split(";"); // semicolon separated list of theme names
// Draft options
String doublePick = metadata.get("DoublePick", "Never");
int maxPodSize = metadata.getInt("MaxPodSize", 8);
int recommendedPodSize = metadata.getInt("RecommendedPodSize", 8);
int maxMatchPlayers = metadata.getInt("MaxMatchPlayers", 2);
String deckType = metadata.get("DeckType", "Normal");
String freeCommander = metadata.get("FreeCommander", "");
res.draftOptions = new DraftOptions(
doublePick,
maxPodSize,
recommendedPodSize,
maxMatchPlayers,
deckType,
freeCommander
);
return res;
}

View File

@@ -0,0 +1,75 @@
package forge.card;
public class DraftOptions {
public enum DoublePick {
NEVER,
FIRST_PICK, // only first pick each pack
WHEN_POD_SIZE_IS_4, // only when pod size is 4, so you can pick two cards each time
ALWAYS // each time you receive a pack, you can pick two cards
};
public enum DeckType {
Normal, // Standard deck, usually 40 cards
Commander // Special deck type for Commander format. Important for selection/construction
}
private DoublePick doublePick = DoublePick.NEVER;
private final int maxPodSize; // Usually 8, but could be smaller for cubes. I guess it could be larger too
private final int recommendedPodSize; // Usually 8, but is 4 for new double pick
private final int maxMatchPlayers; // Usually 2, but 4 for things like Commander or Conspiracy
private final DeckType deckType; // Normal or Commander
private final String freeCommander;
public DraftOptions(String doublePickOption, int maxPodSize, int recommendedPodSize, int maxMatchPlayers, String deckType, String freeCommander) {
this.maxPodSize = maxPodSize;
this.recommendedPodSize = recommendedPodSize;
this.maxMatchPlayers = maxMatchPlayers;
this.deckType = DeckType.valueOf(deckType);
this.freeCommander = freeCommander;
if (doublePickOption != null) {
switch (doublePickOption.toLowerCase()) {
case "firstpick":
doublePick = DoublePick.FIRST_PICK;
break;
case "always":
doublePick = DoublePick.ALWAYS;
break;
case "whenpodsizeis4":
doublePick = DoublePick.WHEN_POD_SIZE_IS_4;
break;
}
}
}
public int getMaxPodSize() {
return maxPodSize;
}
public int getRecommendedPodSize() {
return recommendedPodSize;
}
public DoublePick getDoublePick() {
return doublePick;
}
public DoublePick isDoublePick(int podSize) {
if (doublePick == DoublePick.WHEN_POD_SIZE_IS_4) {
if (podSize != 4) {
return DoublePick.NEVER;
}
// only when pod size is 4, so you can pick two cards each time
return DoublePick.ALWAYS;
}
return doublePick;
}
public int getMaxMatchPlayers() {
return maxMatchPlayers;
}
public DeckType getDeckType() {
return deckType;
}
public String getFreeCommander() {
return freeCommander;
}
}

View File

@@ -115,6 +115,20 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return parts.get(DeckSection.Main);
}
public Pair<Deck, List<PaperCard>> getValid() {
List<PaperCard> unsupported = new ArrayList<>();
for (Entry<DeckSection, CardPool> kv : parts.entrySet()) {
CardPool pool = kv.getValue();
for (Entry<PaperCard, Integer> pc : pool) {
if (pc.getKey().getRules() != null && pc.getKey().getRules().isUnsupported()) {
unsupported.add(pc.getKey());
pool.remove(pc.getKey());
}
}
}
return Pair.of(this, unsupported);
}
public List<PaperCard> getCommanders() {
List<PaperCard> result = Lists.newArrayList();
final CardPool cp = get(DeckSection.Commander);

View File

@@ -207,8 +207,6 @@ public class ImageUtil {
else
editionCode = cp.getEdition().toLowerCase();
String cardCollectorNumber = cp.getCollectorNumber();
// Hack to account for variations in Arabian Nights
cardCollectorNumber = cardCollectorNumber.replace("+", "");
// override old planechase sets from their modified id since scryfall move the planechase cards outside their original setcode
if (cardCollectorNumber.startsWith("OHOP")) {
editionCode = "ohop";
@@ -252,6 +250,11 @@ public class ImageUtil {
: "&face=front");
}
if (cardCollectorNumber.endsWith("")) {
faceParam = "&face=back";
cardCollectorNumber = cardCollectorNumber.substring(0, cardCollectorNumber.length() - 1);
}
return String.format("%s/%s/%s?format=image&version=%s%s", editionCode, encodeUtf8(cardCollectorNumber),
langCode, versionParam, faceParam);
}
@@ -261,6 +264,10 @@ public class ImageUtil {
if (!faceParam.isEmpty()) {
faceParam = (faceParam.equals("back") ? "&face=back" : "&face=front");
}
if (collectorNumber.endsWith("")) {
faceParam = "&face=back";
collectorNumber = collectorNumber.substring(0, collectorNumber.length() - 1);
}
return String.format("%s/%s/%s?format=image&version=%s%s", setCode, encodeUtf8(collectorNumber),
langCode, versionParam, faceParam);
}
@@ -281,8 +288,7 @@ public class ImageUtil {
char c;
for (int i = 0; i < in.length(); i++) {
c = in.charAt(i);
if ((c == '"') || (c == '/') || (c == ':') || (c == '?')) {
} else {
if ((c != '"') && (c != '/') && (c != ':') && (c != '?')) {
out.append(c);
}
}

View File

@@ -195,7 +195,7 @@ public class ForgeScript {
return sa.isKeyword(Keyword.SADDLE);
} else if (property.equals("Station")) {
return sa.isKeyword(Keyword.STATION);
}else if (property.equals("Cycling")) {
} else if (property.equals("Cycling")) {
return sa.isCycling();
} else if (property.equals("Dash")) {
return sa.isDash();
@@ -237,6 +237,8 @@ public class ForgeScript {
return sa.isBoast();
} else if (property.equals("Exhaust")) {
return sa.isExhaust();
} else if (property.equals("Mayhem")) {
return sa.isMayhem();
} else if (property.equals("Mutate")) {
return sa.isMutate();
} else if (property.equals("Ninjutsu")) {

View File

@@ -2222,6 +2222,13 @@ public class GameAction {
}
}
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
// Notify players
for (Player p : game.getPlayers()) {
p.getController().revealUnsupported(unsupported);
}
}
/** Delivers a message to all players. (use reveal to show Cards) */
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
if (saSource != null) {

View File

@@ -125,10 +125,22 @@ public final class GameActionUtil {
// need to be done there before static abilities does reset the card
// These Keywords depend on the Mana Cost of for Split Cards
if (sa.isBasicSpell() && !sa.isLandAbility()) {
if (sa.isBasicSpell()) {
for (final KeywordInterface inst : source.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("Mayhem")) {
if (!source.isInZone(ZoneType.Graveyard) || !source.wasDiscarded() || !source.enteredThisTurn()) {
continue;
}
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Mayhem));
}
if (sa.isLandAbility()) {
continue;
}
if (keyword.startsWith("Escape")) {
if (!source.isInZone(ZoneType.Graveyard)) {
continue;
@@ -166,18 +178,6 @@ public final class GameActionUtil {
}
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Flashback));
} else if (keyword.startsWith("Mayhem")) {
if (!source.isInZone(ZoneType.Graveyard) || !source.wasDiscarded() || !source.enteredThisTurn()) {
continue;
}
// if source has No Mana cost, and Mayhem doesn't have own one,
// Mayhem can't work
if (keyword.equals("Mayhem") && source.getManaCost().isNoCost()) {
continue;
}
alternatives.add(getGraveyardSpellByKeyword(inst, sa, activator, AlternativeCost.Mayhem));
} else if (keyword.startsWith("Harmonize")) {
if (!source.isInZone(ZoneType.Graveyard)) {
continue;
@@ -242,6 +242,7 @@ public final class GameActionUtil {
}
stackCopy.setLastKnownZone(game.getStackZone());
stackCopy.setCastFrom(oldZone);
stackCopy.setCastSA(sa);
lkicheck = true;
stackCopy.clearStaticChangedCardKeywords(false);

View File

@@ -15,6 +15,7 @@ public class GameRules {
private boolean AISideboardingEnabled = false;
private boolean sideboardForAI = false;
private final Set<GameType> appliedVariants = EnumSet.noneOf(GameType.class);
private int simTimeout = 120;
// it's a preference, not rule... but I could hardly find a better place for it
private boolean useGrayText;
@@ -124,4 +125,12 @@ public class GameRules {
public void setWarnAboutAICards(final boolean warnAboutAICards) {
this.warnAboutAICards = warnAboutAICards;
}
public int getSimTimeout() {
return this.simTimeout;
}
public void setSimTimeout(final int duration) {
this.simTimeout = duration;
}
}

View File

@@ -23,6 +23,7 @@ import forge.item.PaperCard;
import forge.util.Localizer;
import forge.util.MyRandom;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.Map.Entry;
@@ -224,6 +225,7 @@ public class Match {
// friendliness
Map<Player, Map<DeckSection, List<? extends PaperCard>>> rAICards = new HashMap<>();
Multimap<Player, PaperCard> removedAnteCards = ArrayListMultimap.create();
Map<Player, List<PaperCard>> unsupported = new HashMap<>();
final FCollectionView<Player> players = game.getPlayers();
final List<RegisteredPlayer> playersConditions = game.getMatch().getPlayers();
@@ -288,22 +290,32 @@ public class Match {
}
}
Deck myDeck = psc.getDeck();
player.setDraftNotes(myDeck.getDraftNotes());
Deck toCheck = psc.getDeck();
if (toCheck == null) {
try {
System.err.println(psc.getPlayer().getName() + " Deck is NULL...");
int val = rules.getGameType().getDeckFormat().getMainRange().getMinimum();
toCheck = new Deck("NULL");
if (val > 0)
toCheck.getMain().add("Wastes", val);
} catch (Exception ignored) {}
}
Pair<Deck, List<PaperCard>> myDeck = toCheck.getValid();
player.setDraftNotes(myDeck.getLeft().getDraftNotes());
Set<PaperCard> myRemovedAnteCards = null;
if (!rules.useAnte()) {
myRemovedAnteCards = getRemovedAnteCards(myDeck);
myRemovedAnteCards = getRemovedAnteCards(myDeck.getLeft());
for (PaperCard cp: myRemovedAnteCards) {
for (Entry<DeckSection, CardPool> ds : myDeck) {
for (Entry<DeckSection, CardPool> ds : myDeck.getLeft()) {
ds.getValue().removeAll(cp);
}
}
}
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
if (myDeck.has(DeckSection.Sideboard)) {
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
preparePlayerZone(player, ZoneType.Library, myDeck.getLeft().getMain(), psc.useRandomFoil());
if (myDeck.getLeft().has(DeckSection.Sideboard)) {
preparePlayerZone(player, ZoneType.Sideboard, myDeck.getLeft().get(DeckSection.Sideboard), psc.useRandomFoil());
// Assign Companion
Card companion = player.assignCompanion(game, person);
@@ -322,7 +334,7 @@ public class Match {
player.shuffle(null);
if (isFirstGame) {
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
Map<DeckSection, List<? extends PaperCard>> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck.getLeft());
if (cardsComplained != null && !cardsComplained.isEmpty()) {
rAICards.put(player, cardsComplained);
}
@@ -337,6 +349,7 @@ public class Match {
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
removedAnteCards.putAll(player, myRemovedAnteCards);
}
unsupported.put(player, myDeck.getRight());
}
final Localizer localizer = Localizer.getInstance();
@@ -347,6 +360,10 @@ public class Match {
if (!removedAnteCards.isEmpty()) {
game.getAction().revealAnte(localizer.getMessage("lblAnteCardsRemoved"), removedAnteCards);
}
if (!unsupported.isEmpty()) {
game.getAction().revealUnsupported(unsupported);
}
}
private void executeAnte(Game lastGame) {

View File

@@ -383,6 +383,9 @@ public final class AbilityFactory {
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
abTgt.setDifferentCMC(true);
}
if (mapParams.containsKey("TargetsWithDifferentNames")) {
abTgt.setDifferentNames(true);
}
if (mapParams.containsKey("TargetsWithEqualToughness")) {
abTgt.setEqualToughness(true);
}

View File

@@ -46,6 +46,9 @@ public class AlterAttributeEffect extends SpellAbilityEffect {
boolean altered = false;
switch (attr.trim()) {
case "Harnessed":
altered = gameCard.setHarnessed(activate);
break;
case "Plotted":
altered = gameCard.setPlotted(activate);

View File

@@ -969,12 +969,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
String prompt;
if (sa.hasParam("OptionalPrompt")) {
prompt = sa.getParam("OptionalPrompt");
} else if (defined) {
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
} else {
if (defined) {
prompt = Localizer.getInstance().getMessage("lblPutThatCardFromPlayerOriginToDestination", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase(), destination.getTranslatedName().toLowerCase());
} else {
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
}
prompt = Localizer.getInstance().getMessage("lblSearchPlayerZoneConfirm", "{player's}", Lang.joinHomogenous(origin, ZoneType::getTranslatedName).toLowerCase());
}
String message = MessageUtil.formatMessage(prompt , decider, player);
if (!decider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneGeneral, message, null)) {

View File

@@ -34,6 +34,9 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
// CR 603.12a if the trigger event or events occur multiple times during the resolution of the spell or ability that created it,
// the reflexive triggered ability will trigger once for each of those times
int amt = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("TriggerAmount", "1"), sa);
if (amt <= 0) {
return;

View File

@@ -54,33 +54,29 @@ public class LifeExchangeEffect extends SpellAbilityEffect {
final int life1 = p1.getLife();
final int life2 = p2.getLife();
final int diff = Math.abs(life1 - life2);
if (sa.hasParam("RememberDifference")) {
final int diff = life1 - life2;
source.addRemembered(diff);
if (life2 > life1) {
// swap players
Player tmp = p2;
p2 = p1;
p1 = tmp;
}
final Map<Player, Integer> lossMap = Maps.newHashMap();
if ((life1 > life2) && p1.canLoseLife() && p2.canGainLife()) {
final int diff = life1 - life2;
if (diff > 0 && p1.canLoseLife() && p2.canGainLife()) {
final int lost = p1.loseLife(diff, false, false);
p2.gainLife(diff, source, sa);
if (lost > 0) {
final Map<Player, Integer> lossMap = Maps.newHashMap();
lossMap.put(p1, lost);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
if (sa.hasParam("RememberOwnLoss") && p1.equals(sa.getActivatingPlayer())) {
source.addRemembered(lost);
}
}
} else if ((life2 > life1) && p2.canLoseLife() && p1.canGainLife()) {
final int diff = life2 - life1;
final int lost = p2.loseLife(diff, false, false);
p1.gainLife(diff, source, sa);
if (lost > 0) {
lossMap.put(p2, lost);
}
} else {
// they are equal or can't be exchanged, so nothing to do
}
if (!lossMap.isEmpty()) { // Run triggers if any player actually lost life
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPIMap(lossMap);
source.getGame().getTriggerHandler().runTrigger(TriggerType.LifeLostAll, runParams, false);
if (sa.hasParam("RememberDifference")) {
source.addRemembered(p1.getLife() - p2.getLife());
}
}
}

View File

@@ -271,10 +271,11 @@ public class ManaEffect extends SpellAbilityEffect {
producedMana.append(abMana.produceMana(mana, p, sa));
}
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
// Only clear express choice after mana has been produced
abMana.clearExpressChoice();
abMana.tapsForMana(sa.getRootAbility(), producedMana.toString());
if (sa.isKeyword(Keyword.FIREBENDING)) {
activator.triggerElementalBend(TriggerType.Firebend);
}

View File

@@ -15,8 +15,9 @@ public class PermanentCreatureEffect extends PermanentEffect {
public String getStackDescription(final SpellAbility sa) {
final CardState source = sa.getCardState();
final StringBuilder sb = new StringBuilder();
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ").append(source.getBasePowerString());
sb.append(" / ").append(source.getBaseToughnessString());
sb.append(CardTranslation.getTranslatedName(source.getName())).append(" - ").append(Localizer.getInstance().getMessage("lblCreature")).append(" ");
sb.append(sa.getParamOrDefault("SetPower", source.getBasePowerString()));
sb.append(" / ").append(sa.getParamOrDefault("SetToughness", source.getBaseToughnessString()));
return sb.toString();
}
}

View File

@@ -17,7 +17,6 @@ import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardUtil;
import forge.game.card.perpetual.PerpetualKeywords;
@@ -282,6 +281,17 @@ public class PumpEffect extends SpellAbilityEffect {
List<Card> tgtCards = getCardsfromTargets(sa);
List<Player> tgtPlayers = getTargetPlayers(sa);
if (sa.hasParam("Optional")) {
final String targets = Lang.joinHomogenous(tgtCards);
final String message = sa.hasParam("OptionQuestion")
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
: Localizer.getInstance().getMessage("lblApplyPumpToTarget", targets);
if (!activator.getController().confirmAction(sa, null, message, null)) {
return;
}
}
List<String> keywords = Lists.newArrayList();
if (sa.hasParam("KW")) {
keywords.addAll(Arrays.asList(sa.getParam("KW").split(" & ")));
@@ -307,8 +317,6 @@ public class PumpEffect extends SpellAbilityEffect {
keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, host, sa);
}
final CardCollection untargetedCards = CardUtil.getRadiance(sa);
if (sa.hasParam("DefinedKW")) {
String defined = sa.getParam("DefinedKW");
if (defined.equals("ChosenType")) {
@@ -394,17 +402,6 @@ public class PumpEffect extends SpellAbilityEffect {
keywords = choice;
}
if (sa.hasParam("Optional")) {
final String targets = Lang.joinHomogenous(tgtCards);
final String message = sa.hasParam("OptionQuestion")
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
: Localizer.getInstance().getMessage("lblApplyPumpToTarget", targets);
if (!activator.getController().confirmAction(sa, null, message, null)) {
return;
}
}
if (sa.hasParam("RememberObjects")) {
host.addRemembered(AbilityUtils.getDefinedObjects(host, sa.getParam("RememberObjects"), sa));
}
@@ -494,7 +491,7 @@ public class PumpEffect extends SpellAbilityEffect {
registerDelayedTrigger(sa, sa.getParam("AtEOT"), tgtCards);
}
for (final Card tgtC : untargetedCards) {
for (final Card tgtC : CardUtil.getRadiance(sa)) {
// only pump things in PumpZone
if (!tgtC.isInZones(pumpZones)) {
continue;

View File

@@ -203,6 +203,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
private boolean unearthed;
private boolean ringbearer;
private boolean monstrous;
private boolean harnessed;
private boolean renowned;
private boolean solved;
private boolean tributed;
@@ -2462,10 +2463,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
}
}
sbLong.append("Enchant ").append(desc).append("\r\n");
} else if (keyword.startsWith("Ripple")) {
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
|| keyword.startsWith("Disguise")
|| keyword.startsWith("Disguise") || keyword.startsWith("Reflect")
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|| keyword.startsWith("Madness:")|| keyword.startsWith("Recover")
|| keyword.startsWith("Reconfigure") || keyword.startsWith("Squad")
@@ -2504,17 +2503,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
sbLong.append(".");
}
if (k.length > 3) {
sbLong.append(". " + k[3]);
sbLong.append(". ").append(k[3]);
}
}
sbLong.append(" (").append(inst.getReminderText()).append(")");
sbLong.append("\r\n");
} else if (keyword.equals("Mayhem")) {
sbLong.append(" (").append(inst.getReminderText()).append(")");
sbLong.append("\r\n");
}
} else if (keyword.startsWith("Reflect")) {
final String[] k = keyword.split(":");
sbLong.append(k[0]).append(" ").append(ManaCostParser.parse(k[1]));
sbLong.append(" (").append(inst.getReminderText()).append(")");
sbLong.append("\r\n");
} else if (keyword.startsWith("Echo")) {
sbLong.append("Echo ");
final String[] upkeepCostParams = keyword.split(":");
@@ -2653,7 +2650,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|| keyword.startsWith("Graft") || keyword.startsWith("Fading") || keyword.startsWith("Vanishing:")
|| keyword.startsWith("Afterlife") || keyword.startsWith("Hideaway") || keyword.startsWith("Toxic")
|| keyword.startsWith("Afflict") || keyword.startsWith ("Poisonous") || keyword.startsWith("Rampage")
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator")) {
|| keyword.startsWith("Renown") || keyword.startsWith("Annihilator") || keyword.startsWith("Ripple")) {
final String[] k = keyword.split(":");
sbLong.append(k[0]).append(" ").append(k[1]).append(" (").append(inst.getReminderText()).append(")");
} else if (keyword.startsWith("Crew")) {
@@ -2969,6 +2966,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
if (monstrous) {
sb.append("Monstrous\r\n");
}
if (harnessed) {
sb.append("Harnessed\r\n");
}
if (renowned) {
sb.append("Renowned\r\n");
}
@@ -6693,6 +6693,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
setRingBearer(false);
}
public final boolean isHarnessed() {
return harnessed;
}
public final boolean setHarnessed(final boolean harnessed0) {
harnessed = harnessed0;
return true;
}
public final boolean isMonstrous() {
return monstrous;
}
@@ -6850,6 +6858,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return exiledSA.isKeyword(Keyword.WARP);
}
public boolean isWebSlinged() {
return getCastSA() != null & getCastSA().isAlternativeCost(AlternativeCost.WebSlinging);
}
public boolean isSpecialized() {
return specialized;
}
@@ -7145,7 +7157,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
return false;
}
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
return false;
}

View File

@@ -376,22 +376,28 @@ public class CardFactory {
}
}
// Build English oracle and translated oracle mapping
// Negative card Id's are for view purposes only
if (c.getId() >= 0) {
// Build English oracle and translated oracle mapping
CardTranslation.buildOracleMapping(face.getName(), face.getOracleText(), variantName);
}
// Name first so Senty has the Card name
// Set name for Sentry reports to be identifiable
c.setName(face.getName());
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
if (c.getId() >= 0) { // Set Triggers & Abilities if not for view
for (Entry<String, String> v : face.getVariables())
c.setSVar(v.getKey(), v.getValue());
for (String r : face.getReplacements())
c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
for (String s : face.getStaticAbilities())
c.addStaticAbility(s);
for (String t : face.getTriggers())
c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
// keywords not before variables
c.addIntrinsicKeywords(face.getKeywords(), false);
// keywords not before variables
c.addIntrinsicKeywords(face.getKeywords(), false);
}
if (face.getDraftActions() != null) {
face.getDraftActions().forEach(c::addDraftAction);
}
@@ -420,7 +426,8 @@ public class CardFactory {
c.setAttractionLights(face.getAttractionLights());
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
if (c.getId() > 0) // Set FactoryAbilities if not for view
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
}
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki, final boolean keepTextChanges) {

View File

@@ -2776,10 +2776,9 @@ public class CardFactoryUtil {
final String cost = params[1];
final StringBuilder sbAttach = new StringBuilder();
sbAttach.append("SP$ Attach | Cost$ ");
sbAttach.append("SP$ Attach | ValidTgts$ Creature | Cost$ ");
sbAttach.append(cost);
sbAttach.append(" | AILogic$ ").append(params.length > 2 ? params[2] : "Pump");
sbAttach.append(" | Bestow$ True | ValidTgts$ Creature");
final SpellAbility sa = AbilityFactory.getAbility(sbAttach.toString(), card);
final StringBuilder sbDesc = new StringBuilder();
@@ -4113,7 +4112,7 @@ public class CardFactoryUtil {
sbValid.append("| ").append(param).append(k[1]);
}
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
+ sbValid.toString() + " | Activator$ Opponent | Description$ "
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
@@ -4154,7 +4153,7 @@ public class CardFactoryUtil {
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
// Target
effect = "Mode$ CantTarget | Protection$ True | ValidCard$ Card.Self | Secondary$ True ";
effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True ";
if (!valid.isEmpty()) {
effect += "| ValidSource$ " + valid;
}
@@ -4162,7 +4161,7 @@ public class CardFactoryUtil {
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
// Attach
effect = "Mode$ CantAttach | Protection$ True | Target$ Card.Self | Secondary$ True ";
effect = "Mode$ CantAttach | Target$ Card.Self | Secondary$ True ";
if (!valid.isEmpty()) {
effect += "| ValidCard$ " + valid;
}
@@ -4183,7 +4182,7 @@ public class CardFactoryUtil {
" | Description$ Chapter abilities of this Saga can't trigger the turn it entered the battlefield unless it has exactly the number of lore counters on it specified in the chapter symbol of that ability.";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Shroud")) {
String effect = "Mode$ CantTarget | ValidCard$ Card.Self | Secondary$ True"
String effect = "Mode$ CantTarget | ValidTarget$ Card.Self | Secondary$ True"
+ " | Description$ Shroud (" + inst.getReminderText() + ")";
inst.addStaticAbility(StaticAbility.create(effect, state.getCard(), state, intrinsic));
} else if (keyword.equals("Skulk")) {

View File

@@ -1244,7 +1244,8 @@ public class CardProperty {
if (property.contains("ControlledBy")) {
FCollectionView<Player> p = AbilityUtils.getDefinedPlayers(source, property.split("ControlledBy")[1], spellAbility);
cards = CardLists.filterControlledBy(cards, p);
if (!cards.contains(card)) {
// Kraven the Hunter LTB trigger
if (!card.isLKI() && !cards.contains(card)) {
return false;
}
}
@@ -1819,6 +1820,10 @@ public class CardProperty {
if (!card.isWarped()) {
return false;
}
} else if (property.equals("webSlinged")) {
if (!card.isWebSlinged()) {
return false;
}
} else if (property.equals("CrewedThisTurn")) {
if (!hasTimestampMatch(card, source.getCrewedByThisTurn())) return false;
} else if (property.equals("CrewedBySourceThisTurn")) {
@@ -1827,6 +1832,10 @@ public class CardProperty {
if (card.getDevouredCards().isEmpty()) {
return false;
}
} else if (property.equals("harnessed")) {
if (!card.isHarnessed()) {
return false;
}
} else if (property.equals("IsMonstrous")) {
if (!card.isMonstrous()) {
return false;

View File

@@ -468,6 +468,9 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable {
return Iterables.getFirst(getIntrinsicSpellAbilities(), null);
}
public final SpellAbility getFirstSpellAbility() {
if (this.card.getCastSA() != null) {
return this.card.getCastSA();
}
return Iterables.getFirst(getNonManaAbilities(), null);
}

View File

@@ -1,5 +1,6 @@
package forge.game.card;
import com.google.common.collect.Lists;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
@@ -7,45 +8,74 @@ import forge.game.trigger.Trigger;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public record CardTraitChanges(Collection<SpellAbility> abilities, Collection<SpellAbility> removedAbilities,
Collection<Trigger> triggers, Collection<ReplacementEffect> replacements, Collection<StaticAbility> staticAbilities,
boolean removeAll, boolean removeNonMana) {
public class CardTraitChanges implements Cloneable {
private List<Trigger> triggers = Lists.newArrayList();
private List<ReplacementEffect> replacements = Lists.newArrayList();
private List<SpellAbility> abilities = Lists.newArrayList();
private List<StaticAbility> staticAbilities = Lists.newArrayList();
private List<SpellAbility> removedAbilities = Lists.newArrayList();
private boolean removeAll = false;
private boolean removeNonMana = false;
public CardTraitChanges(Collection<SpellAbility> spells, Collection<SpellAbility> removedAbilities,
Collection<Trigger> trigger, Collection<ReplacementEffect> res, Collection<StaticAbility> st,
boolean removeAll, boolean removeNonMana) {
if (spells != null) {
this.abilities.addAll(spells);
}
if (removedAbilities != null) {
this.removedAbilities.addAll(removedAbilities);
}
if (trigger != null) {
this.triggers.addAll(trigger);
}
if (res != null) {
this.replacements.addAll(res);
}
if (st != null) {
this.staticAbilities.addAll(st);
}
this.removeAll |= removeAll;
this.removeNonMana |= removeNonMana;
}
/**
* @return the triggers
*/
public Collection<Trigger> getTriggers() {
return Objects.requireNonNullElse(triggers, List.of());
return triggers;
}
/**
* @return the replacements
*/
public Collection<ReplacementEffect> getReplacements() {
return Objects.requireNonNullElse(replacements, List.of());
return replacements;
}
/**
* @return the abilities
*/
public Collection<SpellAbility> getAbilities() {
return Objects.requireNonNullElse(abilities, List.of());
return abilities;
}
/**
* @return the abilities
*/
public Collection<SpellAbility> getRemovedAbilities() {
return Objects.requireNonNullElse(removedAbilities, List.of());
return removedAbilities;
}
/**
* @return the staticAbilities
*/
public Collection<StaticAbility> getStaticAbilities() {
return Objects.requireNonNullElse(staticAbilities, List.of());
return staticAbilities;
}
public boolean isRemoveAll() {
@@ -57,30 +87,53 @@ public record CardTraitChanges(Collection<SpellAbility> abilities, Collection<Sp
}
public CardTraitChanges copy(Card host, boolean lki) {
return new CardTraitChanges(
this.getAbilities().stream().map(sa -> sa.copy(host, lki)).collect(Collectors.toList()),
this.getRemovedAbilities().stream().map(sa -> sa.copy(host, lki)).collect(Collectors.toList()),
this.getTriggers().stream().map(tr -> tr.copy(host, lki)).collect(Collectors.toList()),
this.getReplacements().stream().map(tr -> tr.copy(host, lki)).collect(Collectors.toList()),
this.getStaticAbilities().stream().map(st -> st.copy(host, lki)).collect(Collectors.toList()),
removeAll, removeNonMana
);
try {
CardTraitChanges result = (CardTraitChanges) super.clone();
result.abilities = Lists.newArrayList();
for (SpellAbility sa : this.abilities) {
result.abilities.add(sa.copy(host, lki));
}
result.removedAbilities = Lists.newArrayList();
for (SpellAbility sa : this.removedAbilities) {
result.removedAbilities.add(sa.copy(host, lki));
}
result.triggers = Lists.newArrayList();
for (Trigger tr : this.triggers) {
result.triggers.add(tr.copy(host, lki));
}
result.replacements = Lists.newArrayList();
for (ReplacementEffect re : this.replacements) {
result.replacements.add(re.copy(host, lki));
}
result.staticAbilities = Lists.newArrayList();
for (StaticAbility sa : this.staticAbilities) {
result.staticAbilities.add(sa.copy(host, lki));
}
return result;
} catch (final Exception ex) {
throw new RuntimeException("CardTraitChanges : clone() error", ex);
}
}
public void changeText() {
for (SpellAbility sa : this.getAbilities()) {
for (SpellAbility sa : this.abilities) {
sa.changeText();
}
for (Trigger tr : this.getTriggers()) {
for (Trigger tr : this.triggers) {
tr.changeText();
}
for (ReplacementEffect re : this.getReplacements()) {
for (ReplacementEffect re : this.replacements) {
re.changeText();
}
for (StaticAbility sa : this.getStaticAbilities()) {
for (StaticAbility sa : this.staticAbilities) {
sa.changeText();
}
}

View File

@@ -60,13 +60,12 @@ public class CardView extends GameEntityView {
}
public static TrackableCollection<CardView> getCollection(Iterable<Card> cards) {
if (cards == null) {
return null;
}
TrackableCollection<CardView> collection = new TrackableCollection<>();
for (Card c : cards) {
if (c.getRenderForUI()) { //only add cards that match their card for UI
collection.add(c.getView());
if (cards != null) {
for (Card c : cards) {
if (c != null && c.getRenderForUI()) { //only add cards that match their card for UI
collection.add(c.getView());
}
}
}
return collection;

View File

@@ -167,6 +167,8 @@ public enum CounterEnumType implements CounterType {
FILIBUSTER("FLBTR", 255, 179, 119),
FILM("FILM", 255, 255, 255),
FINALITY("FINAL", 255, 255, 255),
FIRE("FIRE", 240, 30, 35),

View File

@@ -1,6 +1,5 @@
package forge.game.combat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -39,10 +38,12 @@ public class AttackRequirement {
//MustAttack static check
final List<GameEntity> mustAttack = StaticAbilityMustAttack.entitiesMustAttack(attacker);
nAttackAnything += Collections.frequency(mustAttack, attacker);
for (GameEntity e : mustAttack) {
if (e.equals(attacker)) continue;
defenderSpecific.add(e);
if (e.equals(attacker)) {
nAttackAnything++;
} else {
defenderSpecific.add(e);
}
}
for (final GameEntity defender : possibleDefenders) {

View File

@@ -225,7 +225,7 @@ public class CombatUtil {
if (!ge.equals(defender) && ge instanceof Player) {
// found a player which does not goad that creature
// and creature can attack this player or planeswalker
if (!attacker.isGoadedBy((Player) ge) && !ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
if (!attacker.isGoadedBy((Player) ge) && canAttack(attacker, ge)) {
return false;
}
}
@@ -233,17 +233,6 @@ public class CombatUtil {
}
}
// Quasi-goad logic for "Kardur, Doomscourge" etc. that isn't goad but behaves the same
if (defender != null && defender.hasKeyword("Creatures your opponents control attack a player other than you if able.")) {
for (GameEntity ge : getAllPossibleDefenders(attacker.getController())) {
if (!ge.equals(defender) && ge instanceof Player) {
if (!ge.hasKeyword("Creatures your opponents control attack a player other than you if able.") && canAttack(attacker, ge)) {
return false;
}
}
}
}
// CantAttack static abilities
if (StaticAbilityCantAttackBlock.cantAttack(attacker, defender)) {
return false;

View File

@@ -119,7 +119,7 @@ public enum Keyword {
LIVING_METAL("Living metal", SimpleKeyword.class, true, "During your turn, this Vehicle is also a creature."),
LIVING_WEAPON("Living Weapon", SimpleKeyword.class, true, "When this Equipment enters, create a 0/0 black Phyrexian Germ creature token, then attach this to it."),
MADNESS("Madness", KeywordWithCost.class, false, "If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard."),
MAYHEM("Mayhem", KeywordWithCost.class, false, "You may cast this card from your graveyard for %s if you discarded it this turn. Timing rules still apply."),
MAYHEM("Mayhem", Mayhem.class, false, "You may cast this card from your graveyard for %s if you discarded it this turn. Timing rules still apply."),
MELEE("Melee", SimpleKeyword.class, false, "Whenever this creature attacks, it gets +1/+1 until end of turn for each opponent you attacked this combat."),
MENTOR("Mentor", SimpleKeyword.class, false, "Whenever this creature attacks, put a +1/+1 counter on target attacking creature with lesser power."),
MENACE("Menace", SimpleKeyword.class, true, "This creature can't be blocked except by two or more creatures."),

View File

@@ -0,0 +1,25 @@
package forge.game.keyword;
import forge.card.mana.ManaCost;
import forge.game.cost.Cost;
public class Mayhem extends KeywordWithCost {
@Override
protected void parse(String details) {
if (!details.isEmpty()) {
super.parse(details);
} else {
this.cost = new Cost(ManaCost.NO_COST, true);
}
}
@Override
protected String formatReminderText(String reminderText) {
if (this.cost.getTotalMana().isNoCost()) {
return "You may play this card from your graveyard if you discarded it this turn. Timing rules still apply.";
}
return super.formatReminderText(reminderText);
}
}

View File

@@ -44,6 +44,7 @@ import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.AlternativeCost;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.*;
import forge.game.trigger.Trigger;
@@ -454,7 +455,6 @@ public class Player extends GameEntity implements Comparable<Player> {
return false;
}
// Run any applicable replacement effects.
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.LifeGained, lifeGain);
repParams.put(AbilityKey.SourceSA, sa);
@@ -520,7 +520,7 @@ public class Player extends GameEntity implements Comparable<Player> {
return 0;
}
int oldLife = life;
// Run applicable replacement effects
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
repParams.put(AbilityKey.Amount, toLose);
repParams.put(AbilityKey.IsDamage, damage);
@@ -553,7 +553,6 @@ public class Player extends GameEntity implements Comparable<Player> {
}
boolean firstLost = lifeLostThisTurn == 0;
lifeLostThisTurn += toLose;
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(this);
@@ -578,9 +577,6 @@ public class Player extends GameEntity implements Comparable<Player> {
}
public final boolean payLife(final int lifePayment, final SpellAbility cause, final boolean effect) {
return payLife(lifePayment, cause, effect, null);
}
public final boolean payLife(final int lifePayment, final SpellAbility cause, final boolean effect, Map<AbilityKey, Object> params) {
// fast check for pay zero life
if (lifePayment <= 0) {
cause.setPaidLife(0);
@@ -600,9 +596,6 @@ public class Player extends GameEntity implements Comparable<Player> {
if (cause.isReplacementAbility() && effect) {
replaceParams.putAll(cause.getReplacingObjects());
}
if (params != null) {
replaceParams.putAll(params);
}
switch (getGame().getReplacementHandler().run(ReplacementType.PayLife, replaceParams)) {
case Replaced:
return true;
@@ -1085,7 +1078,7 @@ public class Player extends GameEntity implements Comparable<Player> {
}
// CantTarget static abilities
if (StaticAbilityCantTarget.cantTarget(this, sa)) {
if (StaticAbilityCantTarget.cantTarget(this, sa) != null) {
return false;
}
@@ -1718,7 +1711,8 @@ public class Player extends GameEntity implements Comparable<Player> {
}
final Zone zone = game.getZoneOf(land);
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !mayPlay))) {
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !mayPlay
&& (landSa == null || !landSa.isAlternativeCost(AlternativeCost.Mayhem))))) {
return false;
}
}
@@ -1966,10 +1960,11 @@ public class Player extends GameEntity implements Comparable<Player> {
CardState speedFront = speedEffect.getState(CardStateName.Original);
CardState speedBack = speedEffect.getState(CardStateName.Backside);
speedFront.setImageKey("t:speed");
speedFront.setImageKey(StaticData.instance().getOtherImageKey(ImageKeys.SPEED_IMAGE, CardEdition.UNKNOWN_CODE));
speedFront.setName("Start Your Engines!");
speedBack.setImageKey("t:max_speed");
speedBack.setImageKey(StaticData.instance().getOtherImageKey(ImageKeys.MAX_SPEED_IMAGE, CardEdition.UNKNOWN_CODE));
speedBack.setName("Max Speed!");
String label = Localizer.getInstance().getMessage("lblSpeed", this.speed);

View File

@@ -287,6 +287,8 @@ public abstract class PlayerController {
public abstract void revealAnte(String message, Multimap<Player, PaperCard> removedAnteCards);
public abstract void revealAISkipCards(String message, Map<Player, Map<DeckSection, List<? extends PaperCard>>> deckCards);
public abstract void revealUnsupported(Map<Player, List<PaperCard>> unsupported);
// These 2 are for AI
public CardCollectionView cheatShuffle(CardCollectionView list) { return list; }
public Map<DeckSection, List<? extends PaperCard>> complainCardsCantPlayWell(Deck myDeck) { return null; }

View File

@@ -23,21 +23,21 @@ public class PlayerFactoryUtil {
sbValid.append("| ValidSource$ ").append(k[1]);
}
String effect = "Mode$ CantTarget | ValidPlayer$ Player.You | Secondary$ True "
String effect = "Mode$ CantTarget | ValidTarget$ Player.You | Secondary$ True "
+ sbValid.toString() + " | Activator$ Opponent | EffectZone$ Command | Description$ "
+ sbDesc.toString() + " (" + inst.getReminderText() + ")";
final Card card = player.getKeywordCard();
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
} else if (keyword.equals("Shroud")) {
String effect = "Mode$ CantTarget | ValidPlayer$ Player.You | Secondary$ True "
String effect = "Mode$ CantTarget | ValidTarget$ Player.You | Secondary$ True "
+ "| EffectZone$ Command | Description$ Shroud (" + inst.getReminderText() + ")";
final Card card = player.getKeywordCard();
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
} else if (keyword.startsWith("Protection")) {
String valid = CardFactoryUtil.getProtectionValid(keyword, false);
String effect = "Mode$ CantTarget | Protection$ True | ValidPlayer$ Player.You | EffectZone$ Command | Secondary$ True ";
String effect = "Mode$ CantTarget | ValidTarget$ Player.You | EffectZone$ Command | Secondary$ True ";
if (!valid.isEmpty()) {
effect += "| ValidSource$ " + valid;
}
@@ -45,7 +45,7 @@ public class PlayerFactoryUtil {
inst.addStaticAbility(StaticAbility.create(effect, card, card.getCurrentState(), false));
// Attach
effect = "Mode$ CantAttach | Protection$ True | Target$ Player.You | EffectZone$ Command | Secondary$ True ";
effect = "Mode$ CantAttach | Target$ Player.You | EffectZone$ Command | Secondary$ True ";
if (!valid.isEmpty()) {
effect += "| ValidCard$ " + valid;
}

View File

@@ -354,7 +354,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public int totalAmountOfManaGenerated(SpellAbility saPaidFor, boolean multiply) {
int result = 0;
AbilityManaPart mp = getManaPart();
if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor)) {
if (mp != null && mp.meetsManaRestrictions(saPaidFor)) {
result += amountOfManaGenerated(multiply);
}
result += subAbility != null ? subAbility.totalAmountOfManaGenerated(saPaidFor, multiply) : 0;
@@ -671,6 +671,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public final boolean isMadness() {
return isAlternativeCost(AlternativeCost.Madness);
}
public final boolean isMayhem() {
return isAlternativeCost(AlternativeCost.Mayhem);
}
public final boolean isMutate() {
return isAlternativeCost(AlternativeCost.Mutate);
@@ -1500,6 +1504,22 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
}
if (tr.isDifferentCMC() && entity instanceof Card) {
for (final Card c : getTargets().getTargetCards()) {
if (entity != c && c.getCMC() == (((Card) entity).getCMC())) {
return false;
}
}
}
if (tr.isDifferentNames() && entity instanceof Card) {
for (final Card c : getTargets().getTargetCards()) {
if (entity != c && c.sharesNameWith(((Card) entity).getName())) {
return false;
}
}
}
if (tr.isSameController() && entity instanceof Card) {
Player newController;
newController = ((Card) entity).getController();

View File

@@ -59,6 +59,7 @@ public class TargetRestrictions {
private boolean forEachPlayer = false;
private boolean differentControllers = false;
private boolean differentCMC = false;
private boolean differentNames = false;
private boolean equalToughness = false;
private boolean sameController = false;
private boolean withoutSameCreatureType = false;
@@ -621,6 +622,13 @@ public class TargetRestrictions {
this.differentCMC = different;
}
public boolean isDifferentNames() {
return differentNames;
}
public void setDifferentNames(boolean different) {
this.differentNames = different;
}
/**
* @return the equalToughness
*/

View File

@@ -39,36 +39,20 @@ public class StaticAbilityCantTarget {
static String MODE = "CantTarget";
public static boolean cantTarget(final Card card, final SpellAbility spellAbility) {
final Game game = card.getGame();
public static StaticAbility cantTarget(final GameEntity entity, final SpellAbility spellAbility) {
final Game game = entity.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.checkConditions(StaticAbilityMode.CantTarget)) {
continue;
}
if (applyCantTargetAbility(stAb, card, spellAbility)) {
return true;
if (applyCantTargetAbility(stAb, entity, spellAbility)) {
return stAb;
}
}
}
return false;
}
public static boolean cantTarget(final Player player, final SpellAbility spellAbility) {
final Game game = player.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.checkConditions(StaticAbilityMode.CantTarget)) {
continue;
}
if (applyCantTargetAbility(stAb, player, spellAbility)) {
return true;
}
}
}
return false;
return null;
}
/**
@@ -82,57 +66,27 @@ public class StaticAbilityCantTarget {
* the spell/ability
* @return true, if successful
*/
public static boolean applyCantTargetAbility(final StaticAbility stAb, final Card card, final SpellAbility spellAbility) {
if (stAb.hasParam("ValidPlayer")) {
return false;
}
public static boolean applyCantTargetAbility(final StaticAbility stAb, final GameEntity entity, final SpellAbility spellAbility) {
if (entity instanceof Card card) {
if (stAb.hasParam("AffectedZone")) {
if (ZoneType.listValueOf(stAb.getParam("AffectedZone")).stream().noneMatch(zt -> card.isInZone(zt))) {
return false;
}
} else if (!card.isInPlay()) { // default zone is battlefield
return false;
}
Set<ZoneType> zones = stAb.getActiveZone();
if (stAb.hasParam("AffectedZone")) {
boolean inZone = false;
for (final ZoneType zt : ZoneType.listValueOf(stAb.getParam("AffectedZone"))) {
if (card.isInZone(zt)) {
inZone = true;
break;
if (zones != null && zones.contains(ZoneType.Stack)) {
// Enthralling Hold: only works if it wasn't already cast
if (card.getGame().getStack().getSpellMatchingHost(spellAbility.getHostCard()) != null) {
return false;
}
}
if (!inZone) {
return false;
}
} else { // default zone is battlefield
if (!card.isInPlay()) {
return false;
}
}
Set<ZoneType> zones = stAb.getActiveZone();
if (zones != null && zones.contains(ZoneType.Stack)) {
// Enthralling Hold: only works if it wasn't already cast
if (card.getGame().getStack().getSpellMatchingHost(spellAbility.getHostCard()) != null) {
return false;
}
}
if (!stAb.matchesValidParam("ValidCard", card)) {
} else if (stAb.hasParam("AffectedZone")) {
return false;
}
return common(stAb, card, spellAbility);
}
public static boolean applyCantTargetAbility(final StaticAbility stAb, final Player player, final SpellAbility spellAbility) {
if (stAb.hasParam("ValidCard") || stAb.hasParam("AffectedZone")) {
return false;
}
if (!stAb.matchesValidParam("ValidPlayer", player)) {
return false;
}
return common(stAb, player, spellAbility);
}
protected static boolean common(final StaticAbility stAb, GameEntity entity, final SpellAbility spellAbility) {
final Card source = spellAbility.getHostCard();
final Player activator = spellAbility.getActivatingPlayer();
@@ -140,6 +94,10 @@ public class StaticAbilityCantTarget {
return false;
}
if (!stAb.matchesValidParam("ValidTarget", entity)) {
return false;
}
if (!stAb.matchesValidParam("ValidSA", spellAbility)) {
return false;
}

View File

@@ -22,7 +22,6 @@ import java.util.Map;
import com.google.common.collect.Iterables;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
@@ -60,17 +59,8 @@ public class TriggerAttackerBlocked extends Trigger {
return false;
}
if (hasParam("ValidBlocker")) {
@SuppressWarnings("unchecked")
int count = CardLists.getValidCardCount(
(Iterable<Card>) runParams.get(AbilityKey.Blockers),
getParam("ValidBlocker"),
getHostCard().getController(), getHostCard(), this
);
if (count == 0) {
return false;
}
if (!matchesValidParam("ValidBlocker", runParams.get(AbilityKey.Blockers))) {
return false;
}
return true;

View File

@@ -108,9 +108,8 @@ public class TriggerChangesZone extends Trigger {
}
}
Card moved = (Card) runParams.get(AbilityKey.Card);
if (hasParam("ValidCard")) {
Card moved = (Card) runParams.get(AbilityKey.Card);
// CR 603.10a leaves battlefield or GY look back in time
if ("Battlefield".equals(getParam("Origin"))
|| ("Graveyard".equals(getParam("Origin")) && !"Battlefield".equals(getParam("Destination")))) {
@@ -147,8 +146,7 @@ public class TriggerChangesZone extends Trigger {
final Card host = hostCard.getGame().getCardState(hostCard);
final String comparator = condition.length < 2 ? "GE1" : condition[1];
final int referenceValue = AbilityUtils.calculateAmount(host, comparator.substring(2), this);
final Card triggered = (Card) runParams.get(AbilityKey.Card);
final int actualValue = AbilityUtils.calculateAmount(triggered, condition[0], this);
final int actualValue = AbilityUtils.calculateAmount(moved, condition[0], this);
if (!Expressions.compare(actualValue, comparator.substring(0, 2), referenceValue)) {
return false;
}

View File

@@ -514,11 +514,6 @@ public class TriggerHandler {
sa.setActivatingPlayer(p);
}
if (regtrig.hasParam("RememberTriggeringCard")) {
Card triggeredCard = ((Card) sa.getTriggeringObject(AbilityKey.Card));
host.addRemembered(triggeredCard);
}
if (!sa.getActivatingPlayer().isInGame()) {
return;
}

View File

@@ -331,6 +331,12 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
}
}
if (sp instanceof AbilityStatic || (sp.isTrigger() && sp.getTrigger().getOverridingAbility() instanceof AbilityStatic)) {
AbilityUtils.resolve(sp);
// AbilityStatic should do nothing below
return;
}
if (si == null && sp.isActivatedAbility() && !sp.isCopied()) {
// if not already copied use a fresh instance
SpellAbility original = sp;
@@ -354,12 +360,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
addAbilityActivatedThisTurn(sp, source);
}
if (sp instanceof AbilityStatic || (sp.isTrigger() && sp.getTrigger().getOverridingAbility() instanceof AbilityStatic)) {
AbilityUtils.resolve(sp);
// AbilityStatic should do nothing below
return;
}
// The ability is added to stack HERE
si = push(sp, si, id);

View File

@@ -0,0 +1,770 @@
{
"a32x":{
"CPU":"2x Cortex-A76 @ 2GHz 6x Cortex-A55 @ 2GHz",
"SoC":"MediaTek Dimensity 720 (MT6853)"
},
"angler":{
"CPU":"4x Cortex-A57 @ 1.95GHz 4x Cortex-A53 @ 1.55GHz",
"SoC":"Snapdragon 810 MSM8994"
},
"ane":{
"CPU":"4x Cortex-A54 @ 2.3GHz 4x Cortex-A54 @ 1.7GHz",
"SoC":"HiSilicon Kirin 659"
},
"amar_row_wifi":{
"CPU":"8x Cortex-A53 @ 1.8GHz",
"SoC":"Mediatek MT8768"
},
"atlas":{
"CPU":"-",
"SoC":"Intel(R) Core(TM) i5-8200Y @ 1.3GHz"
},
"apq8084":{
"CPU":"4x Krait 450 @ 2.65GHz",
"SoC":"Snapdragon 805 APQ8084"
},
"atoll":{
"CPU":"2x Kryo 465 Gold @ 2.3GHz 6x Kryo 465 Silver @ 1.8GHz",
"SoC":"Snapdragon 720G (SM7125)"
},
"art-l29":{
"CPU":"4x Cortex-A73 @ 2.2GHz 4x Cortex-A53 @ 1.7GHz",
"SoC":"HiSilicon Kirin 710F"
},
"baylake":{
"CPU":"Atom Z3745 @ 1.3GHz",
"SoC":"Intel Atom Z3745"
},
"begonia":{
"CPU":"2x Cortex-176 @ 2GHz 6x Cortex-A55 @ 2GHz",
"SoC":"MediaTek Helios G90T MT6785T"
},
"blueline":{
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.75GHz",
"SoC":"Snapdragon 845"
},
"bullhead":{
"CPU":"4x Cortex-A57 @ 1.8GHz 4x Cortex-A53 @ 1.44GHz",
"SoC":"Snapdragon 808"
},
"capri":{
"CPU":"2x Cortex-A9 @ 1.2GHz",
"SoC":"Broadcom BCM28155"
},
"cepheus":{
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.7GHz",
"SoC":"Snapdragon 855 SM8150"
},
"cheryl":{
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
"SoC":"Snapdragon 835 MSM8998"
},
"cheryl2":{
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.8GHz",
"SoC":"Snapdragon 845 SDM845"
},
"cheetah":{
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Google Tensor G2 (GS201)"
},
"comet":{
"CPU":"1x Cortex-X4 @ 31.GHz 3x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 1.95GHz",
"SoC":"Google Tensor G4 (GS401)"
},
"eureka":{
"CPU":"6x Cortex-A78C @ 2.3 GHz",
"SoC":"Snapdragon XR2 Gen 2 (SM8550)"
},
"felix":{
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Google Tensor G2 (GS201)"
},
"tangorpro":{
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Google Tensor G2 (GS201)"
},
"codina":{
"CPU":"2x Cortex-A9 @ 1.0GHz",
"SoC":"NovaThor U8500"
},
"clovertrail":{
"CPU":"2x Atom Z2560 @ 1.6GHz",
"SoC":"Intel Atom Z2560"
},
"clt":{
"CPU":"4x Cortex-A73 @ 2.36GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"HiSilicon Kirin 970"
},
"els":{
"CPU":"4x Cortex-A76 @ 2.8GHz 4x Cortex-A55 @ 1.9GHz",
"SoC":"HiSilicon Kirin 990 5G"
},
"dandelion":{
"CPU":"8x Cortex-A54 @ 1.5GHz",
"SoC":"MediaTek Helio G25 (MT6762G)"
},
"darcy":{
"CPU":"4x ARM Cortex-A53 4x ARM Cortex-A57",
"SoC":"nVIDIA Tegra X1 T210"
},
"db8520h":{
"CPU":"2x Cortex-A9 @ 1.0GHz",
"SoC":"NovaThor U8500"
},
"dragon":{
"CPU":"4x Cortex-A57 @ 1.9GHz 4x Cortex-A53 @ 1.3GHz",
"SoC":"nVIDIA Tegra X1 T210"
},
"douglas":{
"CPU":"4x Cortex-A53 @ 1.3GHz",
"SoC":"MediaTek MT8163"
},
"eeepad":{
"CPU":"4x Atom Z2520 @ 1.2GHz",
"SoC":"Intel Atom Z2520"
},
"endeavoru":{
"CPU":"4x Cortex-A9 @ 1.5GHz 1x Cortex-A9 @ 0.5GHz",
"SoC":"nVIDIA Tegra 3 AP33"
},
"eml":{
"CPU":"4x Cortex-A73 @ 2.36GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"HiSilicon Kirin 970"
},
"eve":{
"CPU":"i5-7Y56 @ 1.2GHz",
"SoC":"Ambel Lake-Y / Kaby Lake-U/Y"
},
"eva-l19":{
"CPU":"4x Cortex-A72 @ 2.5GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"HiSilicon Kirin 955"
},
"exynos990":{
"CPU":"4x Cortex-A55 @ 2GHz 2x Cortex-A76 @ 2.5GHz 2x Exynos M5 @ 2.7GHz",
"SoC":"Exynos 990"
},
"exynos9611":{
"CPU":"4x Cortex-A73 @ 2.3GHz 4x Cortex-A53 @ 1.7GHz",
"SoC":"Exynos 7 Octa (9611)"
},
"exynos2100":{
"CPU":"1x Cortex-X1 @ 2.9GHz 3x Cortex-A78 @ 2.8GHz 4x Cortex-A55 @ 2.2GHz",
"SoC":"Exynos 2100"
},
"exynos9810":{
"CPU":"4x Exynos M3 @ 2.7GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Exynos 9 (9810)"
},
"exynos9820":{
"CPU":"2 Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.3GHz 4x Cortex-A55 @ 1.9GHz",
"SoC":"Exynos 9 (9820)"
},
"ford":{
"CPU":"4x Cortex-A7 @ 1.3GHz",
"SoC":"MediaTek MT8127"
},
"flo":{
"CPU":"4x Krait 300 @ 1.5GHz",
"SoC":"Snapdragon 600 APQ8064-FLO"
},
"flame":{
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.7GHz",
"SoC":"Snapdragon 855 SM8150"
},
"fleur":{
"CPU":"2x Cortex-A76 @ 2GHz 6x Cortex-A55 @ 2GHz",
"SoC":"MediaTek Helio G96 (MT6781)"
},
"flounder":{
"CPU":"2x nVidia Denver @ 2.5GHz",
"SoC":"nVIDIA Tegra K1 T132"
},
"g3u":{
"CPU":"2x Cortex-A5 @ 1.0GHz",
"SoC":"Snapdragon S4 Play MSM8225"
},
"gee":{
"CPU":"4x Krait 300 @ 1.5GHz",
"SoC":"Snapdragon S4 Pro APQ8064"
},
"grouper":{
"CPU":"4x Cortex-A9 @ 1.3GHz 1x Cortex-A9 @ 0.5GHz",
"SoC":"nVIDIA Tegra 3 T30L"
},
"hammerhead":{
"CPU":"4x Krait 400 @ 2.26GHz",
"SoC":"Snapdragon 800 MSM8974"
},
"hawaii_ss_kylepro":{
"CPU":"2x Cortex-A9 @ 1.2GHz",
"SoC":"Broadcom BCM21664T"
},
"herring":{
"CPU":"1x Cortex-A8 @ 1.0GHz",
"SoC":"Exynos 3 Single 3110"
},
"hollywood":{
"CPU":"4x Kryo 280 HP @ 2.4GHz 4x Kryo 280 LP @ 1.9GHz",
"SoC":"Snapdragon XR2"
},
"k6853v1_64_titan":{
"CPU":"2x Cortex-A76 @ 2Ghz 6x Cortex-A55 @ 2GHz",
"SoC":"MediaTek Dimensity 720 (MT6853)"
},
"kalama":{
"CPU":"1x Cortex-X3 @ 3.3GHz 2x Cortex-A710 @ 2.8GHz 2x Cortex-A715 @ 2.8GHz 3x Cortex-A510 @ 2.0GHz",
"SoC":"Snapdragon 8 Gen 2 (SM8550)"
},
"kona":{
"CPU":"1x Cortex-A77 @ 3.1GHz 3x Cortex-A77 @ 2.4GHz 4x Kryo 585 Silver @ 1.8GHz",
"SoC":"Snapdragon 865 SM8250"
},
"kohaku":{
"CPU":"i5-10210U @ 1.6GHz",
"SoC":"Comet Lake-U"
},
"lahaina":{
"CPU":"1x Cortex-X1 @ 2.8GHz 3x Cortex-A78 @ 2.4GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Snapdragon 888"
},
"liara":{
"CPU":"5 Compute cores 2C+3G",
"SoC":"AMD A4-9120C Radeon R4"
},
"lito":{
"CPU":"2x Cortex-A77 @ 2.1GHz 6x Kryo 560 Silver @ 1.7GHz",
"SoC":"Snapdragon 690 SM6350"
},
"lya":{
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"HiSilicon Kirin 980"
},
"lyo-l01":{
"CPU":"4x Cortex-153 @ 1.3GHz",
"SoC":"MediaTek MT6735"
},
"mako":{
"CPU":"4x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 Pro APQ8064"
},
"marlin":{
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo @ 1.6GHz",
"SoC":"Snapdragon 821 MSM8996 Pro"
},
"med":{
"CPU":"8x Cortex-A54 @ 1.5GHz",
"SoC":"MediaTek MT6762R"
},
"mocha":{
"CPU":"4x Cortex-A15 @2.2GHz",
"SoC":"nVIDIA Tegra K1 T124"
},
"msm8225":{
"CPU":"2x Cortex-A5 @ 1.2GHz",
"SoC":"Snapdragon S4 MSM8225"
},
"msm8226":{
"CPU":"4x Cortex-A7 @ 1.19GHz",
"SoC":"Snapdragon 400 MSM8226"
},
"msm8625":{
"CPU":"2x Cortex-A5 @ 1.2GHz",
"SoC":"Snapdragon S4 MSM8625"
},
"MSM8227":{
"CPU":"2x Krait @ 1GHz",
"SoC":"Snapdragon S4 Plus MSM8227"
},
"msm8627":{
"CPU":"2x Krait @ 1GHz",
"SoC":"Snapdragon S4 Plus MSM8627"
},
"apq8030":{
"CPU":"2x Krait @ 1.2GHz",
"SoC":"Snapdragon S4 Plus APQ8030"
},
"msm8230":{
"CPU":"2x Krait @ 1.2GHz",
"SoC":"Snapdragon S4 Plus MSM8230"
},
"msm8660_surf":{
"CPU":"2x Scorpion @ 1.5GHz",
"SoC":"Snapdragon S3 MSM8260"
},
"msm8630":{
"CPU":"2x Krait @ 1.2GHz",
"SoC":"Snapdragon S4 Plus MSM8630"
},
"msm8930":{
"CPU":"2x Krait @ 1.2GHz",
"SoC":"Snapdragon S4 Plus MSM8930"
},
"msm8937":{
"CPU":"4x Cortex-A53 @ 1.4GHz",
"SoC":"Snapdragon 425 MSM8917"
},
"apq8060a":{
"CPU":"2x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 Plus APQ8060A"
},
"msm8260a":{
"CPU":"2x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 Plus MSM8260A"
},
"msm8660a":{
"CPU":"2x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 Plus MSM8660A"
},
"msm8960":{
"CPU":"2x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 Plus MSM8960"
},
"msm8260a-pro":{
"CPU":"2x Krait 300 @ 1.7GHz",
"SoC":"Snapdragon S4 Pro MSM8260A Pro"
},
"msm8960t":{
"CPU":"2x Krait 300 @ 1.7GHz",
"SoC":"Snapdragon S4 Pro MSM8960T"
},
"msm8960t-pro":{
"CPU":"2x Krait 300 @ 1.7GHz",
"SoC":"Snapdragon S4 Pro MSM8960T Pro"
},
"msm8960ab":{
"CPU":"2x Krait 300 @ 1.7GHz",
"SoC":"Snapdragon S4 Pro MSM8960AB"
},
"msm8960dt":{
"CPU":"2x Krait 300 @ 1.7GHz",
"SoC":"Snapdragon S4 Pro MSM8960DT"
},
"apq8064":{
"CPU":"4x Krait 300 @ 1.5GHz",
"SoC":"Snapdragon 600 APQ8064"
},
"msm8916":{
"CPU":"4x Cortex-A53 @ 1.2GHz",
"SoC":"Snapdragon 410 MSM8916"
},
"msm8953":{
"CPU":"8x Cortex-A53 @ 2.0GHz",
"SoC":"Snapdragon 625 MSM8953"
},
"msm8952":{
"CPU":"8x Cortex-A53 @ 1.2GHz",
"SoC":"Snapdragon 617 MSM8952"
},
"msm8956":{
"CPU":"2x Cortex-A72 @ 1.8GHz 4x Cortex-A53 @ 1.4GHz",
"SoC":"Snapdragon 650 MSM8956"
},
"msm8974":{
"CPU":"4x Krait 400 @ 2.15GHz",
"SoC":"Snapdragon 800 MSM8974"
},
"msm8974pro-ab":{
"CPU":"4x Krait 400 @ 2.26GHz",
"SoC":"Snapdragon 801 MSM8974PRO-AB"
},
"msm8974pro-ac":{
"CPU":"4x Krait 400 @ 2.45GHz",
"SoC":"Snapdragon 801 MSM8974AC"
},
"msm8976":{
"CPU":"4x Cortex-A53 @ 1.4GHz 4x Cortex-A72 @ 1.8GHz",
"SoC":"Snapdragon 652 MSM8976"
},
"msm8976pro":{
"CPU":"4x Cortex-A53 @ 1.4GHz 4x Cortex-A72 @ 1.9GHz",
"SoC":"Snapdragon 653 MSM8976 Pro"
},
"msm8992":{
"CPU":"4x Cortex-A57 @ 1.8GHz 4x Cortex-A53 @ 1.4GHz",
"SoC":"Snapdragon 808 MSM8992"
},
"msm8994":{
"CPU":"4x Cortex-A57 @ 1.95GHz 4x Cortex-A53 @ 1.5GHz",
"SoC":"Snapdragon 810 MSM8994"
},
"msm8996":{
"CPU":"2x Kryo HP @ 1.8GHz 2x Kryo LP @ 1.36GHz",
"SoC":"Snapdragon 820 MSM8996"
},
"msm8996pro":{
"CPU":"2x Kryo HP @ 2.34GHz 2x Kryo LP @ 2.18GHz",
"SoC":"Snapdragon 821 MSM8996 Pro"
},
"msm8998":{
"CPU":"4x Kryo 280 HP @ 2.4GHz 4x Kryo 280 LP @ 1.9GHz",
"SoC":"Snapdragon 835 MSM8998"
},
"msmnile":{
"CPU":"1x Kryo 485 Gold @ 2.8GHz 3x Kryo 485 Gold @ 2.4GHz 4x Kryo 485 Silver @ 1.8GHz",
"SoC":"Snapdragon 855 SM8150"
},
"mt6795t":{
"CPU":"8x Cortex-A53 @ 2.1GHz",
"SoC":"MediaTek Helio X10 MT6795T"
},
"mt6797m":{
"CPU":"2x Cortex-A72 @ 2.1GHz 4x Cortex-A53 @ 1.85GHz 4x Cortex-A53 @ 1.4GHz",
"SoC":"MediaTek Helio X20 MT6797M"
},
"mt6750t":{
"CPU":"8x Cortex-A53 @ 1.5GHz",
"SoC":"MediaTek MT6750T"
},
"mx5":{
"CPU":"8x Cortex-A53 @ 2.1GHz",
"SoC":"MediaTek Helio X10 MT6795T"
},
"mtk6575":{
"CPU":"1x Cortex-A9 @ 1.0GHz",
"SoC":"MediaTek MT6575"
},
"noh":{
"CPU":"4x Cortex-A77 @ 3.1GHz 4x Cortex-A55 @ 2.0GHz",
"SoC":"HiSilicon Kirin 9000"
},
"sdm710":{
"CPU":"2x Kryo 385 Gold @ 2.2GHz 6x Kryo 385 Silver @ 1.7GHz",
"SoC":"Snapdragon 710 SDM710"
},
"sdm845":{
"CPU":"4x Kryo 385 Gold @ 2.8GHz 4x Kryo 385 Silver @ 1.7GHz",
"SoC":"Snapdragon 845 SDM845"
},
"monterey":{
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
"SoC":"Snapdragon 835 MSM8998"
},
"oriole":{
"CPU":"2x Cortex-X1 @ 2.8GHz 2x Cortex-A76 @ 2.2GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Google Tensor (Whitechapel)"
},
"oppo6779_18073":{
"CPU":"2x Cortex-A75 @ 2.2GHz 6x Cortex-A55 @ 2GHz",
"SoC":"Mediatek MT6779 Helio P90"
},
"gt-p7510":{
"CPU":"2x Cortex-A9 @ 1.0GHz",
"SoC":"nVIDIA Tegra 2 T20"
},
"pacific":{
"CPU":"2x @ 2.1GHz 2x @ 1.6GHz",
"SoC":"Snapdragon 820E Embedded"
},
"piranha":{
"CPU":"2x Cortex-A9 @ 1.0GHz",
"SoC":"Texas Instruments OMAP4430"
},
"pro5":{
"CPU":"4xCortex-56 @ 2.1GHz 4x Cortex-53 @ 1.5GHz",
"SoC":"Exynos 7 Octa 7420"
},
"pro7plus":{
"CPU":"2x Cortex-A73 @ 2.6GHz 4x Cortex-A53 @ 2.2GHz 4x Cortex-A35 @ 1.9GHz",
"SoC":"MediaTek Helio X30 MT6799"
},
"pxa986":{
"CPU":"2x Cortex-A9 @ 1.2GHz",
"SoC":"Marvell PXA988"
},
"pxa19xx":{
"CPU":"4x Cortex-A53 @ 1.25GHz",
"SoC":"Maxvell Armada PXA1908"
},
"rhea_ss_corsicass":{
"CPU":"1x Cortex-A9 @ 0.9GHz",
"SoC":"Broadcom BCM21654"
},
"panther":{
"CPU":"1x Cortex-X1 @ 2.8GHz 2x Cortex-A78 @ 2.3GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Google Tensor G2"
},
"prada":{
"CPU":"4x Cortex-A53 @ 1.4 GHz 4x Cortex-A53 @ 1.1GHz",
"SoC":"Snapdragon 430 MSM8937"
},
"pro6plus":{
"CPU":"4x Exynos M1 @ 1.97GHz 4x Cortex-A53 @ 1.48GHz",
"SoC":"Exynos 8 Octa 8890"
},
"universal3110":{
"CPU":"1x Cortex-A8 @ 1.2GHz",
"SoC":"Exynos 3 Single 3110"
},
"universal9820":{
"CPU":"2x Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.3GHz 4x Cortex-A55 @ 1.95GHz",
"SoC":"Exynos 9 9820"
},
"redfin":{
"CPU":"2x Kryo 475 Gold @ 2.4GHz 2x Kryo 475 Gold 2.2GHz 6x Kryo 475 Silver @ 1.8GHz ",
"SoC":"Snapdragon 765/765G"
},
"s5e9925":{
"CPU":"1x Cortex-X2 @ 2.8GHZ 3x Cortex-A710 @ 2.5GHz 4x Cortex-A510 @ 1.8GHz",
"SoC":"Exynos 2200"
},
"saturn":{
"CPU":"4x Krait 450 @ 2.45GHz",
"SoC":"Snapdragon 805 APQ8084"
},
"sailfish":{
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo @ 1.6GHz",
"SoC":"Snapdragon 821 MSM8996 Pro"
},
"sdm660":{
"CPU":"4x Kryo 260 HP @ 2.2GHz 4x Kryo 260 LP @ 1.8GHz",
"SoC":"Snapdragon 660"
},
"sc-02b":{
"CPU":"1x Cortex-A8 @ 1.2GHz",
"SoC":"Exynos 3 Single 3110"
},
"sch-i905":{
"CPU":"2x Cortex-A9 @ 1.0GHz",
"SoC":"nVIDIA Tegra 2 T20"
},
"sc7730s":{
"CPU":"4x Cortex-A7 @ 1.3GHz",
"SoC":"Spreadtrum SC7730S"
},
"shamu":{
"CPU":"4x Krai 450 @ 2.65GHz",
"SoC":"Snapdragon 805 APQ8084AB"
},
"shiba":{
"CPU":"1x Cortex-X3 @ 2.9GHz 4x Cortex-A715 @ 2.3GHz 4x Cortex-A510 @ 1.7GHz",
"SoC":"Google Tensor G3 (GS301)"
},
"sm6150":{
"CPU":"2x Kryo 460 Gold @ 2GHz 6x 460 Kryo Silver @ 1.7GHz",
"SoC":"Snapdragon 675 SM6150"
},
"smdkc110":{
"CPU":"1x Cortex-A8 @ 1.2GHz",
"SoC":"Exynos 3 Single 3110"
},
"sun":{
"CPU":"2x Oryon @ 4.5GHz 6x Oryon @ 3.5GHz",
"SoC":"Snapdragon 9 Elite (SM8750)"
},
"ums512_25c10": {
"CPU":"2 Cortex-A75 @2GHz 6x Cortex-A55 @ 2 GHz",
"SoC":"Unisoc Tiger T618"
},
"universal3250":{
"CPU":"2x Cortex-A7 @ 1.0GHz",
"SoC":"Exynos 2 Dual 3250"
},
"universal3470":{
"CPU":"4x Cortex-A7 @ 1.4GHz",
"SoC":"Exynos 3 Quad 3470"
},
"universal3475":{
"CPU":"4x Cortex-A7 @ 1.3GHz",
"SoC":"Exynos 3 Quad 3475"
},
"universal4210":{
"CPU":"2x Cortex-A9 @ 1.4GHz",
"SoC":"Exynos 4 Dual 4210"
},
"universal4212":{
"CPU":"2x Cortex-A9 @ 1.5GHz",
"SoC":"Exynos 4 Dual 4212"
},
"universal4412":{
"CPU":"4x Cortex-A9 @ 1.4GHz",
"SoC":"Exynos 4 Quad 4412"
},
"smdk4x12":{
"CPU":"4x Cortex-A9 @ 1.4GHz",
"SoC":"Exynos 4 Quad 4412"
},
"universal4415":{
"CPU":"4x Cortex-A9 @ 1.5GHz",
"SoC":"Exynos 4 Quad 4415"
},
"universal5250":{
"CPU":"2x Cortex-A15 @ 1.7GHz",
"SoC":"Exynos 5 Dual 5250"
},
"universal5260":{
"CPU":"2x Cortex-A15 @ 1.7GHz 4x Cortex-A7 @ 1.3GHz",
"SoC":"Exynos 5 Hexa 5260"
},
"universal5410":{
"CPU":"4x Cortex-A15 @ 1.6GHz 4x Cortex-A7 @ 1.2GHz",
"SoC":"Exynos 5 Octa 5410"
},
"universal5420":{
"CPU":"4x Cortex-A15 @ 1.9GHz 4x Cortex-A7 @ 1.3GHz",
"SoC":"Exynos 5 Octa 5420"
},
"universal5422":{
"CPU":"4x Cortex-A15 @ 2.1GHz 4x Cortex-A7 @ 1.5GHz",
"SoC":"Exynos 5 Octa 5422"
},
"universal5430":{
"CPU":"4x Cortex-A15 @ 1.8GHz 4x Cortex-A7 @ 1.3GHz",
"SoC":"Exynos 5 Octa 5430"
},
"universal5800":{
"CPU":"4x Cortex-A15 @ 2.0GHz 4x Cortex-A7 @ 1.3GHz",
"SoC":"Exynos 5 Octa 5800"
},
"universal5433":{
"CPU":"4x Cortex-A57 @ 1.9GHz 4x Cortex-A53 @ 1.3GHz",
"SoC":"Exynos 7 Octa 5433"
},
"universal7420":{
"CPU":"4x Cortex-A57 @ 2.1GHz 4x Cortex-A53 @ 1.5GHz",
"SoC":"Exynos 7 Octa 7420"
},
"universal7570":{
"CPU":"8x Cortex-A53 @ 1.4GHz",
"SoC":"Exynos 7 Quad 7570"
},
"universal7580":{
"CPU":"8x Cortex-A53 @ 1.6GHz",
"SoC":"Exynos 7 Octa 7580"
},
"universal7870":{
"CPU":"8x Cortex-A53 @ 1.6GHz",
"SoC":"Exynos 7 Octa 7870"
},
"universal7880":{
"CPU":"8x Cortex-A53 @ 1.9GHz",
"SoC":"Exynos 7 Octa 7880"
},
"universal7872":{
"CPU":"2x Cortex-A73 @ 2.0GHz 4x Cortex-A53 @ 1.6GHz",
"SoC":"Exynos 7 Hexa 7872"
},
"universal7885":{
"CPU":"2x Cortex-A73 @ 2.2GHz 6x Cortex-A53 @ 1.6GHz",
"SoC":"Exynos 7 Octa 7885"
},
"universal8890":{
"CPU":"4x Cortex-A53 @ 1.6GHz 4x Samsung Exynos M1 @ 2.6GHz",
"SoC":"Exynos 8 Octa 8890"
},
"universal8895":{
"CPU":"4x Samsung Exynos M1 @ 2.3GHz 4x Cortex-A53 @ 1.6GHz",
"SoC":"Exynos 9 Octa 8895"
},
"universal9810":{
"CPU":"4x Samsung Exynos M3 @ 2.8GHz 4x Cortex-A55 @ 1.7GHz",
"SoC":"Exynos 9 Series 9810"
},
"universal9825":{
"CPU":"2x Samsung Exynos M4 @ 2.7GHz 2x Cortex-A75 @ 2.4GHz 4x Cortex-A55 @ 1.9GHz",
"SoC":"Exynos 9 Series 9825"
},
"ville":{
"CPU":"2x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 MSM8290"
},
"vog":{
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"HiSilicon Kirin 980"
},
"venus":{
"CPU":"1x Cortex-X1 @ 2.8GHz 3x Cortex-A78 @ 2.4GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Snapdragon 888 SM8350"
},
"vtr":{
"CPU":"4x Cortex-A73 @ 2.4GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"HiSilicon Kirin 960"
},
"taimen":{
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
"SoC":"Snapdragon 835 MSM8998"
},
"tn8":{
"CPU":"4x Cortex-A15 @2.2GHz",
"SoC":"nVIDIA Tegra K1 T124"
},
"taro":{
"CPU":"1x Cortex-X2 @ 3GHz 3x Cortex-A710 @ 2.5GHz 4x Cortex-A510 @ 1.8GHz",
"SoC":"Snapdragon 8 Gen 1 (SM8450)"
},
"thebes":{
"CPU":"2x Cortex-A15 @ 1.5GHz 2x Cortex-A7 @ 1.2GHz",
"SoC":"MediaTek MT8135"
},
"trinket":{
"CPU":"4x Kryo 260 HP @ 2GHz 4x Kryo 260 LP @ 1.8GHz",
"SoC":"Snapdragon 665 SM6125"
},
"tuna":{
"CPU":"2x Cortex-A9 @ 1.2GHz",
"SoC":"TI OMAP 4460"
},
"vu2kt":{
"CPU":"2x Krait @ 1.5GHz",
"SoC":"Snapdragon S4 Plus MSM8960"
},
"walleye":{
"CPU":"4x Kryo 280 HP @ 2.45GHz 4x Kryo 280 LP @ 1.9GHz",
"SoC":"Snapdragon 835 MSM8998"
},
"wikipad":{
"CPU":"4x Cortex-A9 @ 1.3GHz 1x Cortex-A9 @ 0.5GHz",
"SoC":"nVIDIA Tegra 3 T30L"
},
"qc_reference_phone":{
"CPU":"2x Kryo HP @ 2.15GHz 2x Kryo LP @ 1.36GHz",
"SoC":"Snapdragon 820 MSM8996"
},
"z4u":{
"CPU":"4x Cortex-A5 @ 1.2GHz",
"SoC":"Snapdragon 200 MSM8225Q"
},
"zs600kl":{
"CPU":"4x Kryo 385 Gold @ 3GHz 4x Kryo 385 Silver @ 1.8GHz",
"SoC":"Snapdragon 845"
},
"mt6765v":{
"CPU":"4x Cortex-A53 @ 2.3GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"Mediatek MT6765G Helio G35 (12 nm)"
},
"k65v1_64_bsp_titan_rat":{
"CPU":"4x Cortex-A53 @ 2.3GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"Mediatek MT6765 Helio P35 (12nm)"
},
"careena":{
"CPU":"AMD 670F00h",
"SoC":"AMD A4-9120C RADEON R4, 5 COMPUTE CORES 2C+3G"
},
"sion":{
"CPU":"i3-8130U CPU 2.2GHz SoC",
"SoC":"i3-8130U CPU 2,2 GHz soc"
},
"biloba":{
"CPU":"2x Cortex-A75 @ 2.0GHz 6x Cortex-A55 @ 1.8GHz",
"SoC":"Mediatek MT6769Z Helio G85 (12nm)"
},
"ele":{
"CPU":"2x Cortex-A76 @ 2.6GHz 2x Cortex-A76 @ 1.9GHz 4x Cortex-A55 @ 1.8GHz",
"SoC":"Kirin 980 (7 nm)"
},
"krane":{
"CPU":"4x Cortex-A73 @ 2.0GHz + 4x Cortex-A53 @ 2.0GHz",
"SoC":"MediaTekHelio P60T (12nm)"
},
"pineapple":{
"CPU":"1x Cortex-X4 @ 3.3GHz 3x Cortex-A720 @ 3.2GHz 2x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 2.3GHz",
"SoC":"Qualcomm SM8650-AB Snapdragon 8 Gen 3"
},
"s5e9945":{
"CPU":"1x Cortex-X4 @ 3.2GHz 2x Cortex-A720 @ 2.9GHz 3x Cortex-A720 @ 2.6GHz 4x Cortex-A520 @ 2.0GHz",
"SoC":"Exynos 2400"
},
"a12":{
"CPU":"4x Cortex-A53 @ 2.35GHz 4x Cortex-A53 @ 1.8GHz",
"SoC":"MediaTek Helio P35 (MT6765)"
}
}

View File

@@ -67,10 +67,12 @@ import forge.util.ThreadUtil;
import io.sentry.protocol.Device;
import io.sentry.protocol.OperatingSystem;
import org.apache.commons.lang3.tuple.Pair;
import org.json.JSONObject;
import org.jupnp.DefaultUpnpServiceConfiguration;
import org.jupnp.android.AndroidUpnpServiceConfiguration;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
@@ -188,7 +190,26 @@ public class Main extends AndroidApplication {
boolean permissiongranted = checkPermission();
Gadapter = new AndroidAdapter(getContext());
String cpu = "";
String soc = "";
boolean getChipset = false;
// database.json source: https://github.com/xTheEc0/Android-Device-Hardware-Specs-Database
try {
InputStream is = getAssets().open("database.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
JSONObject db = new JSONObject(new String(buffer, StandardCharsets.UTF_8));
JSONObject board = db.getJSONObject(Build.BOARD);
cpu = board.get("CPU").toString();
soc = board.get("SoC").toString();
getChipset = true;
} catch (Exception e) {
cpu = getCpuName();
soc = Build.BOARD;
getChipset = false;
}
// Device Info
Device device = new Device();
device.setId(Build.ID);
@@ -197,8 +218,8 @@ public class Main extends AndroidApplication {
device.setBrand(Build.BRAND);
device.setManufacturer(Build.MANUFACTURER);
device.setMemorySize(memInfo.totalMem);
device.setCpuDescription(getCpuName());
device.setChipset(Build.HARDWARE + " " + Build.BOARD);
device.setCpuDescription(cpu);
device.setChipset(soc);
// OS Info
OperatingSystem os = new OperatingSystem();
os.setName("Android");
@@ -206,7 +227,7 @@ public class Main extends AndroidApplication {
os.setBuild(Build.DISPLAY);
os.setRawDescription(getAndroidOSName());
initForge(Gadapter, new HWInfo(device, os), permissiongranted, totalMemory, isTabletDevice(getContext()));
initForge(Gadapter, new HWInfo(device, os, getChipset), permissiongranted, totalMemory, isTabletDevice(getContext()));
}
private void crossfade(View contentView, View previousView) {

View File

@@ -228,9 +228,9 @@ public enum CSubmenuDraft implements ICDoc {
return;
}
final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
if (VSubmenuDraft.SINGLETON_INSTANCE.isSingleSelected()) {
// Single opponent
final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
int indx = 0;
for (@SuppressWarnings("unused") Deck d : opponentDecks.getAiDecks()) {
indx++;
@@ -242,10 +242,16 @@ public enum CSubmenuDraft implements ICDoc {
combo.addItem("Gauntlet");
//combo.addItem("Tournament");
} else {
int size = opponentDecks.getAiDecks().size();
combo.addItem("2");
combo.addItem("3");
combo.addItem("4");
combo.addItem("5");
if (size > 2) {
combo.addItem("3");
}
if (size >= 4) {
combo.addItem("4");
combo.addItem("5");
}
}
}
}

View File

@@ -120,6 +120,7 @@ public enum VSubmenuSealed implements IVSubmenu<CSubmenuSealed> {
grpPanel.add(radAll, "w 200px!, h 30px!");
radSingle.setSelected(true);
grpPanel.add(cbOpponent, "w 200px!, h 30px!");
pnlStart.removeAll();
pnlStart.setLayout(new MigLayout("insets 0, gap 0, wrap 2"));
pnlStart.setOpaque(false);
pnlStart.add(grpPanel, "gapright 20");

View File

@@ -130,6 +130,10 @@ public class SimulateMatch {
}
}
if (params.containsKey("c")) {
rules.setSimTimeout(Integer.parseInt(params.get("c").get(0)));
}
sb.append(" - ").append(Lang.nounWithNumeral(nGames, "game")).append(" of ").append(type);
System.out.println(sb.toString());
@@ -163,6 +167,7 @@ public class SimulateMatch {
System.out.println("\tT - Type of tournament to run with all provided decks (Bracket, RoundRobin, Swiss)");
System.out.println("\tP - Amount of players per match (used only with Tournaments, defaults to 2)");
System.out.println("\tF - format of games, defaults to constructed");
System.out.println("\tc - Clock flag. Set the maximum time in seconds before calling the match a draw, defaults to 120.");
System.out.println("\tq - Quiet flag. Output just the game result, not the entire game log.");
}
@@ -176,7 +181,7 @@ public class SimulateMatch {
TimeLimitedCodeBlock.runWithTimeout(() -> {
mc.startGame(g1);
sw.stop();
}, 120, TimeUnit.SECONDS);
}, mc.getRules().getSimTimeout(), TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("Stopping slow match as draw");
} catch (Exception | StackOverflowError e) {

View File

@@ -664,6 +664,11 @@ public class PlayerControllerForTests extends PlayerController {
// TODO test this!
}
@Override
public void revealUnsupported(Map<Player, List<PaperCard>> unsupported) {
// test this!
}
@Override
public List<PaperCard> chooseCardsYouWonToAddToDeck(List<PaperCard> losses) {
// TODO Auto-generated method stub

View File

@@ -51,7 +51,7 @@ public class GameLauncher {
os.setBuild(si.getOperatingSystem().getVersionInfo().getBuildNumber());
os.setRawDescription(si.getOperatingSystem() + " x" + si.getOperatingSystem().getBitness());
totalRAM = Math.round(si.getHardware().getMemory().getTotal() / 1024f / 1024f);
hw = new HWInfo(device, os);
hw = new HWInfo(device, os, false);
} catch (Exception e) {
e.printStackTrace();
}

View File

@@ -150,7 +150,7 @@ public class Forge implements ApplicationListener {
scope.getContexts().setOperatingSystem(hwInfo.os());
});
}
GuiBase.setDeviceInfo(hwInfo, AndroidAPI, totalRAM);
GuiBase.setDeviceInfo(hwInfo, AndroidAPI, totalRAM, deviceAdapter.getDownloadsDir());
}
return app;
}
@@ -171,18 +171,7 @@ public class Forge implements ApplicationListener {
//install our error handler
ExceptionHandler.registerErrorHandling();
//init hwInfo to log
HWInfo info = GuiBase.getHWInfo();
if (info != null) {
System.out.println(
"##########################################\n" +
"APP: Forge v." + GuiBase.getInterface().getCurrentVersion() +
"\nDEV: " + info.device().getName() +
"\nCPU: " + info.device().getCpuDescription() +
"\nRAM: " + GuiBase.getDeviceRAM() + " MB" +
"\nOS: " + info.os().getRawDescription() +
"\n##########################################"
);
}
System.out.println(GuiBase.getHWInfo());
// closeSplashScreen() is called early on non-Windows OS so it will not crash, LWJGL3 bug on AWT Splash.
if (OperatingSystem.isWindows())
getDeviceAdapter().closeSplashScreen();

View File

@@ -1419,6 +1419,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent {
*/
public int copyDeck() {
for (int i = 0; i < MAX_DECK_COUNT; i++) {
if (i >= getDeckCount()) addDeck();
if (isEmptyDeck(i)) {
decks.set(i, (Deck) deck.copyTo(deck.getName() + " (" + Forge.getLocalizer().getMessage("lblCopy") + ")"));
return i;

View File

@@ -83,7 +83,7 @@ public class DeckSelectScene extends UIScene {
private void layoutDeckButtons() {
for (int i = 0; i < AdventurePlayer.current().getDeckCount(); i++)
addDeckButton(Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1), i);
addDeckButton(i);
}
private void addDeck(){
@@ -144,6 +144,7 @@ public class DeckSelectScene extends UIScene {
}
private void updateDeckButton(int index) {
if (!buttons.containsKey(index)) addDeckButton(index);
buttons.get(index).setText(Current.player().getDeck(index).getName());
buttons.get(index).getTextraLabel().layout();
buttons.get(index).layout();
@@ -177,8 +178,9 @@ public class DeckSelectScene extends UIScene {
}
}
private TextraButton addDeckButton(String name, int i) {
private TextraButton addDeckButton(int i) {
TextraButton button = Controls.newTextButton("-");
String name = Forge.getLocalizer().getMessage("lblDeck") + ": " + (i + 1);
button.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {

View File

@@ -192,18 +192,19 @@ public class DuelScene extends ForgeScene {
@Override
public void enter() {
GameHUD.getInstance().unloadAudio();
Set<GameType> appliedVariants = new HashSet<>();
GameType mainGameType;
if (eventData != null && eventData.eventRules != null) {
appliedVariants.add(eventData.eventRules.gameType);
mainGameType = eventData.eventRules.gameType;
} else {
appliedVariants.add(GameType.Adventure);
mainGameType = GameType.Adventure;
}
Set<GameType> appliedVariants = EnumSet.of(mainGameType);
AdventurePlayer advPlayer = Current.player();
List<RegisteredPlayer> players = new ArrayList<>();
applyAdventureDeckRules();
applyAdventureDeckRules(mainGameType.getDeckFormat());
int playerCount = 1;
EnemyData currentEnemy = enemy.getData();
for (int i = 0; i < 8 && currentEnemy != null; i++) {
@@ -393,16 +394,25 @@ public class DuelScene extends ForgeScene {
private static final String PLACEHOLDER_ATTRACTION = "Coin-Operated Pony";
private static final String PLACEHOLDER_CONTRAPTION = "Automatic Fidget Spinner";
private void applyAdventureDeckRules() {
private void applyAdventureDeckRules(DeckFormat format) {
//Can't just keep the player from entering a battle if their deck is invalid. So instead we'll just edit their deck.
CardPool mainSection = playerDeck.getMain(), attractions = playerDeck.get(DeckSection.Attractions), contraptions = playerDeck.get(DeckSection.Contraptions);
DeckFormat format = DeckFormat.Adventure;
removeExcessCopies(mainSection, format);
removeExcessCopies(attractions, format);
removeExcessCopies(contraptions, format);
int missingCards = Config.instance().getConfigData().minDeckSize - mainSection.countAll();
int mainSize = mainSection.countAll();
int maxDeckSize = format == DeckFormat.Adventure ? Integer.MAX_VALUE : format.getMainRange().getMaximum();
int excessCards = mainSize - maxDeckSize;
if (excessCards > 0) {
List<PaperCard> removals = Aggregates.random(mainSection.toFlatList(), excessCards);
mainSection.removeAllFlat(removals);
}
int minDeckSize = format == DeckFormat.Adventure ? Config.instance().getConfigData().minDeckSize : format.getMainRange().getMinimum();
int missingCards = minDeckSize - mainSize;
if (missingCards > 0) //Replace unknown cards for a Wastes.
mainSection.add(PLACEHOLDER_MAIN, missingCards);

View File

@@ -20,6 +20,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
captureException(e, key, subData);
}
}
@@ -33,6 +34,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -45,6 +47,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -57,6 +60,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -69,6 +73,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -81,6 +86,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -95,6 +101,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
stream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -107,6 +114,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
captureException(e, key, subData);
}
}
@@ -119,6 +127,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
captureException(e, key, subData);
}
}
@@ -132,6 +141,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}
@@ -147,6 +157,7 @@ public class SaveFileData extends HashMap<String, byte[]> {
objStream.flush();
put(key, stream.toByteArray());
} catch (IOException e) {
put("IOException", e.toString().getBytes());
e.printStackTrace();
}
}

View File

@@ -26,53 +26,50 @@ import java.util.zip.InflaterInputStream;
/**
* Represents everything that will be saved, like the player and the world.
*/
public class WorldSave {
public class WorldSave {
static final public int AUTO_SAVE_SLOT =-1;
static final public int QUICK_SAVE_SLOT =-2;
static final public int INVALID_SAVE_SLOT =-3;
static final WorldSave currentSave=new WorldSave();
static final public int AUTO_SAVE_SLOT = -1;
static final public int QUICK_SAVE_SLOT = -2;
static final public int INVALID_SAVE_SLOT = -3;
static final WorldSave currentSave = new WorldSave();
public WorldSaveHeader header = new WorldSaveHeader();
private final AdventurePlayer player=new AdventurePlayer();
private final World world=new World();
private final PointOfInterestChanges.Map pointOfInterestChanges= new PointOfInterestChanges.Map();
private final AdventurePlayer player = new AdventurePlayer();
private final World world = new World();
private final PointOfInterestChanges.Map pointOfInterestChanges = new PointOfInterestChanges.Map();
private final SignalList onLoadList=new SignalList();
private final SignalList onLoadList = new SignalList();
public final World getWorld()
{
public final World getWorld() {
return world;
}
public AdventurePlayer getPlayer()
{
public AdventurePlayer getPlayer() {
return player;
}
public void onLoad(Runnable run)
{
public void onLoad(Runnable run) {
onLoadList.add(run);
}
public PointOfInterestChanges getPointOfInterestChanges(String id)
{
if(!pointOfInterestChanges.containsKey(id))
pointOfInterestChanges.put(id,new PointOfInterestChanges());
public PointOfInterestChanges getPointOfInterestChanges(String id) {
if (!pointOfInterestChanges.containsKey(id))
pointOfInterestChanges.put(id, new PointOfInterestChanges());
return pointOfInterestChanges.get(id);
}
static public boolean load(int currentSlot) {
String fileName = WorldSave.getSaveFile(currentSlot);
if(!new File(fileName).exists())
if (!new File(fileName).exists())
return false;
new File(getSaveDir()).mkdirs();
try {
try(FileInputStream fos = new FileInputStream(fileName);
InflaterInputStream inf = new InflaterInputStream(fos);
ObjectInputStream oos = new ObjectInputStream(inf))
{
try (FileInputStream fos = new FileInputStream(fileName);
InflaterInputStream inf = new InflaterInputStream(fos);
ObjectInputStream oos = new ObjectInputStream(inf)) {
currentSave.header = (WorldSaveHeader) oos.readObject();
SaveFileData mainData=(SaveFileData)oos.readObject();
SaveFileData mainData = (SaveFileData) oos.readObject();
currentSave.player.load(mainData.readSubData("player"));
GamePlayerUtil.getGuiPlayer().setName(currentSave.player.getName());
try {
@@ -95,9 +92,11 @@ public class WorldSave {
}
return true;
}
public static boolean isSafeFile(String name) {
return filenameToSlot(name)!= INVALID_SAVE_SLOT;
return filenameToSlot(name) != INVALID_SAVE_SLOT;
}
static public int filenameToSlot(String name) {
if (name.equals("auto_save.sav"))
return AUTO_SAVE_SLOT;
@@ -131,10 +130,10 @@ public class WorldSave {
public static WorldSave generateNewWorld(String name, boolean male, int race, int avatarIndex, ColorSet startingColorIdentity, DifficultyData diff, AdventureModes mode, int customDeckIndex, CardEdition starterEdition, long seed) {
currentSave.world.generateNew(seed);
currentSave.pointOfInterestChanges.clear();
boolean chaos=mode==AdventureModes.Chaos;
boolean custom=mode==AdventureModes.Custom;
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity,diff,mode,customDeckIndex,starterEdition);
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
boolean chaos = mode == AdventureModes.Chaos;
boolean custom = mode == AdventureModes.Custom;
Deck starterDeck = Config.instance().starterDeck(startingColorIdentity, diff, mode, customDeckIndex, starterEdition);
currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff);
currentSave.player.setWorldPosY((int) (currentSave.world.getData().playerStartPosY * currentSave.world.getData().height * currentSave.world.getTileSize()));
currentSave.player.setWorldPosX((int) (currentSave.world.getData().playerStartPosX * currentSave.world.getData().width * currentSave.world.getTileSize()));
currentSave.onLoadList.emit();
@@ -142,46 +141,103 @@ public class WorldSave {
}
public boolean autoSave() {
return save("auto save"+ SaveLoadScene.instance().getSaveFileSuffix(),AUTO_SAVE_SLOT);
return save("auto save" + SaveLoadScene.instance().getSaveFileSuffix(), AUTO_SAVE_SLOT);
}
public boolean quickSave() {
return save("quick save"+ SaveLoadScene.instance().getSaveFileSuffix(),QUICK_SAVE_SLOT);
return save("quick save" + SaveLoadScene.instance().getSaveFileSuffix(), QUICK_SAVE_SLOT);
}
public boolean quickLoad() {
return load(QUICK_SAVE_SLOT);
}
public boolean save(String text, int currentSlot) {
header.name = text;
String fileName = WorldSave.getSaveFile(currentSlot);
String oldFileName = fileName.replace(".sav", ".old");
new File(getSaveDir()).mkdirs();
File currentFile = new File(fileName);
File backupFile = new File(oldFileName);
if (currentFile.exists())
currentFile.renameTo(backupFile);
try {
try(FileOutputStream fos = new FileOutputStream(fileName);
DeflaterOutputStream def= new DeflaterOutputStream(fos);
ObjectOutputStream oos = new ObjectOutputStream(def))
{
header.saveDate= new Date();
oos.writeObject(header);
SaveFileData mainData=new SaveFileData();
mainData.store("player",currentSave.player.save());
mainData.store("world",currentSave.world.save());
mainData.store("worldStage", WorldStage.getInstance().save());
mainData.store("pointOfInterestChanges",currentSave.pointOfInterestChanges.save());
try (FileOutputStream fos = new FileOutputStream(fileName);
DeflaterOutputStream def = new DeflaterOutputStream(fos);
ObjectOutputStream oos = new ObjectOutputStream(def)) {
SaveFileData player = currentSave.player.save();
SaveFileData world = currentSave.world.save();
SaveFileData worldStage = WorldStage.getInstance().save();
SaveFileData poiChanges = currentSave.pointOfInterestChanges.save();
String message = getExceptionMessage(player, world, worldStage, poiChanges);
if (!message.isEmpty()) {
oos.close();
fos.close();
restoreBackup(oldFileName, fileName);
announceError(message);
return true;
}
SaveFileData mainData = new SaveFileData();
mainData.store("player", player);
mainData.store("world", world);
mainData.store("worldStage", worldStage);
mainData.store("pointOfInterestChanges", poiChanges);
if (mainData.readString("IOException") != null) {
oos.close();
fos.close();
restoreBackup(oldFileName, fileName);
announceError("Please check forge.log for errors.");
return true;
}
header.saveDate = new Date();
oos.writeObject(header);
oos.writeObject(mainData);
}
} catch (IOException e) {
e.printStackTrace();
return false;
restoreBackup(oldFileName, fileName);
announceError("Please check forge.log for errors.");
return true;
}
Config.instance().getSettingData().lastActiveSave = WorldSave.filename(currentSlot);
Config.instance().saveSettings();
if (backupFile.exists())
backupFile.delete();
return true;
}
public void restoreBackup(String oldFilename, String currentFilename) {
File f = new File(currentFilename);
if (f.exists())
f.delete();
File b = new File(oldFilename);
if (b.exists())
b.renameTo(new File(currentFilename));
}
public String getExceptionMessage(SaveFileData... datas) {
StringBuilder message = new StringBuilder();
for (SaveFileData data : datas) {
String s = data.readString("IOException");
if (s != null)
message.append(s).append("\n");
}
return message.toString();
}
private void announceError(String message) {
currentSave.player.getCurrentGameStage().setExtraAnnouncement("Error Saving File!\n" + message);
}
public void clearChanges() {
pointOfInterestChanges.clear();
}

View File

@@ -44,7 +44,10 @@ public class BugReportDialog extends FScreen { //use screen rather than dialog s
BugReporter.sendSentry();
Forge.back();
});
btnSave.setCommand(e -> BugReporter.saveToFile(tvDetails.text));
btnSave.setCommand(e -> {
BugReporter.saveToFile(tvDetails.text);
Forge.back();
});
btnDiscard.setCommand(e -> Forge.back());
if (showExitAppBtn) {
btnExit.setCommand(e -> Forge.exit(true));

View File

@@ -915,7 +915,7 @@ public abstract class ItemManager<T extends InventoryItem> extends FContainer im
Iterable<Entry<T, Integer>> items = pool;
if (useFilter) {
Predicate<Entry<T, Integer>> pred = x -> filterPredicate.test(x.getKey());
Predicate<Entry<T, Integer>> pred = x -> x != null && filterPredicate.test(x.getKey());
items = IterableUtil.filter(pool, pred);
}
model.addItems(items);

View File

@@ -78,6 +78,59 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
private Supplier<List<Group>> groups = Suppliers.memoize(ArrayList::new);
private Function<Entry<? extends InventoryItem, Integer>, ?> fnIsFavorite = ColumnDef.FAVORITE.fnDisplay, fnPrice = null;
private class SafeList<T> {
private final List<T> internalList;
private final Object lock = new Object(); // Object for synchronization
private SafeList() {
this.internalList = new ArrayList<>();
}
private void add(T element) {
synchronized (lock) {
internalList.add(element);
}
}
private T get(int index) {
synchronized (lock) {
return internalList.get(index);
}
}
private T remove(int index) {
synchronized (lock) {
return internalList.remove(index);
}
}
private int size() {
synchronized (lock) {
return internalList.size();
}
}
private void clear() {
synchronized (lock) {
internalList.clear();
}
}
private boolean isEmpty() {
synchronized (lock) {
return internalList.isEmpty();
}
}
private boolean addAll(Collection c) {
synchronized (lock) {
return internalList.addAll(c);
}
}
// Add other list operations as needed, ensuring synchronization
}
private class ExpandCollapseButton extends FLabel {
private boolean isAllCollapsed;
@@ -330,11 +383,17 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
if (group.getBottom() < visibleTop) {
continue;
}
for (Pile pile : group.piles) {
for (int i = 0; i < group.piles.size(); i++) {
Pile pile = group.piles.get(i);
if (pile == null)
continue;
if (group.getBottom() < visibleTop) {
continue;
}
for (ItemInfo item : pile.items) {
for (int j = 0; j < pile.items.size(); j++) {
ItemInfo item = pile.items.get(j);
if (item == null)
continue;
if (item.getTop() >= visibleTop) {
return item;
}
@@ -454,7 +513,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
//use TreeMap to build pile set so iterating below sorts on key
ColumnDef groupPileBy = groupBy == null ? pileBy : groupBy.getGroupPileBy(i, pileBy);
Map<Comparable<?>, Pile> piles = new TreeMap<>();
for (ItemInfo itemInfo : group.items) {
for (int j = 0; j < group.items.size(); j++) {
ItemInfo itemInfo = group.items.get(j);
if (itemInfo == null)
continue;
Comparable<?> key = groupPileBy.fnSort.apply(itemInfo);
if (key != null && !piles.containsKey(key)) {
piles.put(key, new Pile());
@@ -490,7 +552,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
Pile pile = new Pile();
x = 0;
for (ItemInfo itemInfo : group.items) {
for (int j = 0; j < group.items.size(); j++) {
ItemInfo itemInfo = group.items.get(j);
if (itemInfo == null)
continue;
itemInfo.pos = CardStackPosition.Top;
if (pile.items.size() == columnCount) {
@@ -517,7 +582,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
for (int j = 0; j < group.piles.size(); j++) {
Pile pile = group.piles.get(j);
y = pileY;
for (ItemInfo itemInfo : pile.items) {
for (int k = 0; k < pile.items.size(); k++) {
ItemInfo itemInfo = pile.items.get(k);
if (itemInfo == null)
continue;
itemInfo.pos = CardStackPosition.BehindVert;
itemInfo.setBounds(x, y, itemWidth, itemHeight);
y += dy;
@@ -549,15 +617,24 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
if (group.isCollapsed && pileBy == null) {
//Piles won't have been generated in this case.
for(ItemInfo itemInfo : group.items) {
for (int i = 0; i < group.items.size(); i++) {
ItemInfo itemInfo = group.items.get(i);
if (itemInfo == null)
continue;
itemInfo.index = index++;
orderedItems.get().add(itemInfo);
}
continue;
}
for (Pile pile : group.piles) {
for (ItemInfo itemInfo : pile.items) {
for (int i = 0; i < group.piles.size(); i++) {
Pile pile = group.piles.get(i);
if (pile == null)
continue;
for (int j = 0; j < pile.items.size(); j++) {
ItemInfo itemInfo = pile.items.get(j);
if (itemInfo == null)
continue;
itemInfo.index = index++;
orderedItems.get().add(itemInfo);
}
@@ -655,7 +732,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
@Override
public int getIndexOfItem(T item) {
for (Group group : groups.get()) {
for (ItemInfo itemInfo : group.items) {
for (int i = 0; i < group.items.size(); i++) {
ItemInfo itemInfo = group.items.get(i);
if (itemInfo == null)
continue;
if (itemInfo.item == item) {
//if group containing item is collapsed, expand it so the item can be selected and has a valid index
if (group.isCollapsed) {
@@ -838,8 +918,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
}
private class Group extends FScrollPane {
private final List<ItemInfo> items = new ArrayList<>();
private final List<Pile> piles = new ArrayList<>();
private final SafeList<ItemInfo> items = new SafeList<>();
private final SafeList<Pile> piles = new SafeList<>();
private final String name;
private boolean isCollapsed;
private float scrollWidth;
@@ -896,7 +976,8 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
float visibleLeft = getScrollLeft();
float visibleRight = visibleLeft + getWidth();
for (Pile pile : piles) {
for (int i = 0; i < piles.size(); i++) {
Pile pile = piles.get(i);
if (pile == null)
continue;
if (pile.getRight() < visibleLeft) {
@@ -964,7 +1045,7 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
}
private class Pile extends FDisplayObject {
private final List<ItemInfo> items = new ArrayList<>();
private final SafeList<ItemInfo> items = new SafeList<>();
@Override
public void draw(Graphics g) {
@@ -972,7 +1053,10 @@ public class ImageView<T extends InventoryItem> extends ItemView<T> {
final float visibleBottom = visibleTop + getScroller().getHeight();
ItemInfo skippedItem = null;
for (ItemInfo itemInfo : items) {
for (int i = 0; i < items.size(); i++) {
ItemInfo itemInfo = items.get(i);
if (itemInfo == null)
continue;
if (itemInfo.getBottom() < visibleTop) {
continue;
}

View File

@@ -192,7 +192,7 @@ public final class ItemListView<T extends InventoryItem> extends ItemView<T> {
@Override
protected void onRefresh() {
list.setListData(model.getOrderedList());
list.setListData(new ArrayList<>(model.getOrderedList()));
}
@Override

View File

@@ -1,66 +1,237 @@
0nderzeeboot
Adajone
add-le
Agetian
allentiak
Alumi
Alwayssnarky
amang876
amikano
Ampersandnz
antoniomartinelli
Aoki-Kujo
apantel
Arguas
ArtjomsPorss
asvitkine
austeregrim
Austinio7116
autumnmyst
AvacynAngel
Ayora29
beemaisonp
Benjamin
BigCrunch22
BlindGuyNW
bountygiver
brngl
brockfanning
CCTV-1
cdietschrun
ChristopherLoper
Churrufli
cicoa
CodeOfTheRing
CollinJ
Compterra
CTimmerman
Cyantime
datuilabs
davidjdiebold
davison-cf
dennisfriedrichsen
dennisvlahos
DerpaholicRex
dev-id
Dnaynu
DorkmasterFlek
Dracontes
drdemp72
DrDev
Drecon84
earno12
edg444
EfourC
EldritchBimbo
Ellios77
Elwin
entrailmix
Eradev
EvanMurawski
excessum
fernandomfgomes
FLAREdirector-mse
Flair
Fregnor
Fulgur14
gabrielmop
Gadget2013
Glorax
Gos
Grimm
GroundThing
gsonnier333
guytrash
Hanmac
HassanSky81
HeitorBittenc
Heitorpayao
henges
hovergoat
hungriestPigeon
hypercross
Hythonia
IceBen4444
icy-
imakunee
Indigo Dragon
invalidCards
isaacbutterfield
Ivniinvi
JakeLoustone
Jamin Collins
jcb936
jeb886
Jefik37
Jetz72
jjayers99
jkarlsson
jochemvanthull-cpu
John
JohnWilliams77
joongiealexkim
Jorilx
jpvorenk
jumpinjackie
junytang
jyockey
karlek
keineahnungking
kenizl86
Kev-inBacon
kevlahnota
klaxnek
Klisz
kms70847
krafczyk
KrazyTheFox
kvn1338
LAHardman
LargeGutSalesman
lemtom
leriomaggio
loud1990
Luke
lukeway
Lykrast
MAC-GH
maforget
Magpie
magpie514
magusnebula
MarcoFazioRandom
Marek14
marthinwurer
Marvel
maulet218
mcrawford
mctubbies
MD200210
medusa
Meerkov
mewtwo15026
MIC132
MikeS-NZ
misha-colbourne
MisterVitoPro
mmw125
Monkey Gland Sauce
Morgenmvffel
mousep
mtwilliams14
Myrd
myyk
NCat39
nefigah
neoFuzz
NicolasCunha
NikolayXHD
NimSpork
Nionios
NishacIroCode
Northmoc
nshcat
nthoron
NuWiSan
OgreBattlecruiser
pakoito
paulsnoops
pduran5
petersul
pferreir
pfirpfel
pfps
pochiel
pvishalkeerthan
rawbeans
remggo
rikimbo
riku4470
Ral
Robbatog
rpg2014
rsnively
Ryan1729
ryanehamil
SapphiCat
schnautzr
Seravy
SeravySensei
SethMilliken
Shedletsky
shenshinoman
Simisays
Sirspud
SladeWilson
Sloth
slyfox7777777
Snoops
Sol
SprinkleMeTimbers
squee1968
stubobis1
Sunnovah
Swordshine
Swordshinehjy
Suthro
Svaldan
t-w-o-s-a-t
tehdiplomat
The Cheese Stands Alone
The-Wolverine28
TheBlackMarvel
thedevnull
TheLastNarwhal
thenobletheif
Tillerino
timmermac
TimothyWright95
tjtillman
tojammot
tool4ever
torridus
TrueFuFLeaderG
TwentyToedToad
twosat
Valensior
verifiedtm
wcoldren
Wild-W
xanxer6rB
XavierMD
Xyx
ZacharyDeganutti
Zimtente
Zuchinni
XavierMD
(If you think your name should be on this list, add it with your next contribution)

View File

@@ -3,6 +3,6 @@ ManaCost:4
Types:Creature Avatar
PT:3/4
K:Vigilance
A:AB$ CopySpellAbility | Cost$ 1 T | TgtPrompt$ Select target activated or triggered ability you control from a colorless source | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card.Colorless | AILogic$ AlwaysCopyActivatedAbilities | MayChooseTarget$ True | SpellDescription$ Copy target activated or triggered ability you control from a colorless source. You may choose new targets for the copy. (Mana abilities can't be targeted.)
A:AB$ CopySpellAbility | Cost$ 1 T | TgtPrompt$ Select target activated or triggered ability you control from a colorless source | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Card.Colorless,Emblem | AILogic$ AlwaysCopyActivatedAbilities | MayChooseTarget$ True | SpellDescription$ Copy target activated or triggered ability you control from a colorless source. You may choose new targets for the copy. (Mana abilities can't be targeted.)
DeckHints:Type$Artifact|Eldrazi
Oracle:Vigilance\n{1}, {T}: Copy target activated or triggered ability you control from a colorless source. You may choose new targets for the copy. (Mana abilities can't be targeted.)

View File

@@ -0,0 +1,10 @@
Name:Agent Venom
ManaCost:2 B
Types:Legendary Creature Symbiote Soldier Hero
PT:2/3
K:Flash
K:Menace
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other+!token+YouCtrl | Execute$ TrigDraw | TriggerDescription$ Whenever another nontoken creature you control dies, you draw a card and lose 1 life.
SVar:TrigDraw:DB$ Draw | SubAbility$ DBLoseLife
SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
Oracle:Flash\nMenace\nWhenever another nontoken creature you control dies, you draw a card and lose 1 life.

View File

@@ -0,0 +1,9 @@
Name:Alien Symbiosis
ManaCost:1 B
Types:Enchantment Aura
K:Enchant:Creature
SVar:AttachAILogic:Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Menace | AddType$ Symbiote | Description$ Enchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types.
S:Mode$ Continuous | Affected$ Card.Self | MayPlay$ True | AffectedZone$ Graveyard | EffectZone$ Graveyard | RaiseCost$ Discard<1/Card> | Description$ You may cast this card from your graveyard by discarding a card in addition to paying its other costs.
DeckHas:Ability$Graveyard|Discard
Oracle:Enchant creature\nEnchanted creature gets +1/+1, has menace, and is a Symbiote in addition to its other types.\nYou may cast this card from your graveyard by discarding a card in addition to paying its other costs.

View File

@@ -0,0 +1,7 @@
Name:Amazing Acrobatics
ManaCost:1 U U
Types:Instant
A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBCounter,DBTap
SVar:DBCounter:DB$ Counter | TargetType$ Spell | ValidTgts$ Card | TgtPrompt$ Counter target spell | SpellDescription$ Counter target spell.
SVar:DBTap:DB$ Tap | TargetMin$ 1 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Choose one or two target creatures | SpellDescription$ Tap one or two target creatures.
Oracle:Choose one or both —\n• Counter target spell.\n• Tap one or two target creatures.

View File

@@ -0,0 +1,11 @@
Name:Ambassador of Evendo
ManaCost:G U
Types:Creature Insect Advisor
PT:1/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigRandom | TriggerDescription$ Landfall — Whenever a land you control enters, a random land card in your library perpetually gains "Whenever this land becomes tapped, draw a card."
SVar:TrigRandom:DB$ ChooseCard | Choices$ Land.YouOwn | ChoiceZone$ Library | AtRandom$ True | Amount$ 1 | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Defined$ ChosenCard | Triggers$ LandTapDraw | Duration$ Perpetual | SubAbility$ DBCleanup
SVar:LandTapDraw:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever this land becomes tapped, draw a card.
SVar:TrigDraw:DB$ Draw
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
Oracle:Landfall — Whenever a land you control enters, a random land card in your library perpetually gains "Whenever this land becomes tapped, draw a card."

View File

@@ -3,6 +3,6 @@ ManaCost:2 U
Types:Enchantment Aura
K:Enchant:Creature
SVar:AttachAILogic:Pump
S:Mode$ CantTarget | ValidCard$ Creature.EnchantedBy | ValidSA$ Spell | Description$ Enchanted creature can't be the target of spells.
S:Mode$ CantTarget | ValidTarget$ Creature.EnchantedBy | ValidSA$ Spell | Description$ Enchanted creature can't be the target of spells.
S:Mode$ CantAttach | ValidCard$ Aura.Other | Target$ Creature.EnchantedBy | Description$ Enchanted creature can't be enchanted by other Auras.
Oracle:Enchant creature\nEnchanted creature can't be the target of spells and can't be enchanted by other Auras.

View File

@@ -0,0 +1,12 @@
Name:Arachne, Psionic Weaver
ManaCost:2 W
Types:Legendary Creature Spider Human Hero
PT:3/3
K:Web-slinging:W
K:ETBReplacement:Other:ChoosePlayer
SVar:ChoosePlayer:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent to look at their hand | SubAbility$ DBLook | SpellDescription$ As NICKNAME enters, look at an opponent's hand, then choose a card type other than creature.
SVar:DBLook:DB$ RevealHand | Defined$ ChosenPlayer | Look$ True | SubAbility$ DBChooseCardType
SVar:DBChooseCardType:DB$ ChooseType | Defined$ You | Type$ Card | InvalidTypes$ Creature | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenPlayer$ True
S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.ChosenType | Type$ Spell | Amount$ 1 | Activator$ Player | Description$ Spells of the chosen type cost {1} more to cast.
Oracle:Web-slinging {W} (You may cast this spell for {W} if you also return a tapped creature you control to its owner's hand.)\nAs Arachne enters, look at an opponent's hand, then choose a card type other than creature.\nSpells of the chosen type cost {1} more to cast.

View File

@@ -3,6 +3,7 @@ ManaCost:5 G
Types:Creature Spider
PT:5/7
K:Reach
A:AB$ ChangeZone | Cost$ tapXType<1/Spider> | Hidden$ True | Origin$ Library | OriginAlternative$ Graveyard | Destination$ Battlefield | ChangeType$ Card.YouOwn+namedArachnus Web | SpellDescription$ Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle.
A:AB$ Pump | Cost$ tapXType<1/Spider> | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | SubAbility$ DBChange
SVar:DBChange:DB$ ChangeZone | Hidden$ True | Origin$ Library | OriginAlternative$ Graveyard | Destination$ Battlefield | ChangeType$ Card.YouOwn+namedArachnus Web | AttachedTo$ ParentTarget | SpellDescription$ Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle.
DeckHints:Name$Arachnus Web & Type$Spider
Oracle:Reach\nTap an untapped Spider you control: Search your graveyard and/or library for a card named Arachnus Web and put it onto the battlefield attached to target creature. If you search your library this way, shuffle.

View File

@@ -0,0 +1,15 @@
Name:Araña, Heart of the Spider
ManaCost:1 R W
Types:Legendary Creature Spider Human Hero
PT:3/3
T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, put a +1/+1 counter on target attacking creature.
SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature
T:Mode$ DamageDone | ValidSource$ Creature.modified+YouCtrl | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn. (Equipment, Auras you control, and counters are modifications.)
SVar:TrigExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect
SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | ExileOnMoved$ Exile | RememberObjects$ Remembered | SubAbility$ DBCleanup
SVar:STPlay:Mode$ Continuous | MayPlay$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Exile the top card of your library. You may play that card this turn.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:PlayMain1:TRUE
DeckHas:Ability$Counters
DeckHints:Type$Aura|Equipment
Oracle:Whenever you attack, put a +1/+1 counter on target attacking creature.\nWhenever a modified creature you control deals combat damage to a player, exile the top card of your library. You may play that card this turn. (Equipment, Auras you control, and counters are modifications.)

View File

@@ -4,7 +4,7 @@ Types:Creature Phoenix
PT:3/2
K:Flying
K:Haste
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Graveyard | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigReturn | OptionalDecider$ You | TriggerDescription$ At the beginning of combat on your turn, if you've cast three or more instant and sorcery spells this turn, return CARDNAME from your graveyard to the battlefield.
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Graveyard | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, if you've cast three or more instant and sorcery spells this turn, return CARDNAME from your graveyard to the battlefield.
SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield
SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl
DeckNeeds:Type$Instant|Sorcery

View File

@@ -5,5 +5,5 @@ K:Enchant:Creature
SVar:AttachAILogic:Pump
S:Mode$ CantBlockBy | ValidAttacker$ Creature.EnchantedBy | ValidBlocker$ Creature.Artifact | Description$ Enchanted creature can't be blocked by artifact creatures.
R:Event$ DamageDone | Prevent$ True | ActiveZones$ Battlefield | ValidTarget$ Creature.EnchantedBy | ValidSource$ Artifact | Description$ Prevent all damage that would be dealt to enchanted creature by artifact sources.
S:Mode$ CantTarget | ValidCard$ Card.EnchantedBy | ValidSource$ Artifact | Description$ Enchanted creature can't be the target of abilities from artifact sources.
S:Mode$ CantTarget | ValidTarget$ Card.EnchantedBy | ValidSource$ Artifact | Description$ Enchanted creature can't be the target of abilities from artifact sources.
Oracle:Enchant creature\nEnchanted creature can't be blocked by artifact creatures.\nPrevent all damage that would be dealt to enchanted creature by artifact sources.\nEnchanted creature can't be the target of abilities from artifact sources.

View File

@@ -3,7 +3,7 @@ ManaCost:G
Types:Instant
A:SP$ Effect | ReplacementEffects$ AntiMagic | StaticAbilities$ STCantBeTarget | SpellDescription$ Spells you control can't be countered by blue or black spells this turn, and creatures you control can't be the targets of blue or black spells this turn.
SVar:AntiMagic:Event$ Counter | ValidSA$ Spell.YouCtrl | ValidCause$ Spell.Blue,Spell.Black | Layer$ CantHappen | Description$ Spells you control can't be countered by blue or black spells this turn.
SVar:STCantBeTarget:Mode$ CantTarget | ValidCard$ Creature.YouCtrl | ValidSource$ Card.Blue,Card.Black | ValidSA$ Spell | Description$ Creatures you control can't be the targets of blue or black spells this turn.
SVar:STCantBeTarget:Mode$ CantTarget | ValidTarget$ Creature.YouCtrl | ValidSource$ Card.Blue,Card.Black | ValidSA$ Spell | Description$ Creatures you control can't be the targets of blue or black spells this turn.
AI:RemoveDeck:All
AI:RemoveDeck:Random
Oracle:Spells you control can't be countered by blue or black spells this turn, and creatures you control can't be the targets of blue or black spells this turn.

View File

@@ -0,0 +1,10 @@
Name:Axavar, Fate Thief
ManaCost:2 B R
Types:Legendary Creature Drix Pirate
PT:4/3
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | Execute$ TrigDiscard | TriggerDescription$ Void — At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, discard a card, then heist target opponent's library.
SVar:TrigDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBHeist
SVar:DBHeist:DB$ Heist | ValidTgts$ Opponent
SVar:X:Count$Void.1.0
K:Warp:BR
Oracle:Void — At the beginning of your end step, if a nonland permanent left the battlefield this turn or a spell was warped this turn, discard a card, then heist target opponent's library.\nWarp {B/R}

View File

@@ -0,0 +1,8 @@
Name:Bagel and Schmear
ManaCost:1
Types:Artifact Food
A:AB$ PutCounter | PreCostDesc$ Share — | Cost$ W T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.
A:AB$ GainLife | PreCostDesc$ Nosh — | Cost$ 2 T Sac<1/CARDNAME> | LifeAmount$ 3 | SubAbility$ DBDraw | SpellDescription$ You gain 3 life and draw a card.
SVar:DBDraw:DB$ Draw
DeckHas:Ability$LifeGain|Sacrifice|Counters
Oracle:Share — {W}, {T}, Sacrifice this artifact: Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.\nNosh — {2}, {T}, Sacrifice this artifact: You gain 3 life and draw a card.

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