Compare commits

..

104 Commits

Author SHA1 Message Date
Blacksmith
9a30d78f54 [maven-release-plugin] prepare release forge-1.6.30 2019-11-11 02:45:50 +00:00
Blacksmith
db98ce160b Update README.txt for release 2019-11-11 02:43:16 +00:00
Sol
ce8b5b53e0 Merge branch 'update-release-files' into 'master'
Update Release files

See merge request core-developers/forge!2276
2019-11-11 02:32:02 +00:00
Sol
0c4055726c Update ANNOUNCEMENTS.txt 2019-11-11 02:26:43 +00:00
Michael Kamensky
64ae4bae0c Merge branch 'newBranch' into 'master'
Update some GUI elements on Mobile Networkplay

See merge request core-developers/forge!2274
2019-11-10 06:21:46 +00:00
Anthony Calosa
044cc793e8 Merge remote-tracking branch 'remotes/core/master' into newBranch 2019-11-09 21:04:48 +08:00
Michael Kamensky
e310dc30d7 Merge branch 'pioneerdeckgen' into 'master'
Updated standard deckgen data

See merge request core-developers/forge!2275
2019-11-09 11:41:28 +00:00
Anthony Calosa
f239755249 Fix "controls" when alternating human vs ai, then ai vs ai play on mobile forge,
update refreshfield, update targeting arrows on 3 to 4 players
(shows attacked player on 3/4 player match...)
2019-11-09 19:28:21 +08:00
austinio7116
57686c0554 Updated standard deckgen data
(cherry picked from commit 75f1a60)
2019-11-09 09:04:06 +00:00
Anthony Calosa
5ecde572c3 Merge remote-tracking branch 'remotes/core/master' into newBranch 2019-11-09 06:27:14 +08:00
Anthony Calosa
23e9974950 Update some GUI elements on networkplay -> client 2019-11-09 06:20:13 +08:00
Anthony Calosa
ec98d128f1 prevent npe mojhosto 2019-11-09 06:16:58 +08:00
swordshine
5c07951604 Merge branch 'german-translation' into 'master'
fix typos

See merge request core-developers/forge!2273
2019-11-08 00:43:36 +00:00
Dagin Svezek
7be34625f6 fix typos 2019-11-07 14:03:24 +00:00
swordshine
5f6cb893ef Merge branch 'master' into 'master'
Added puzzle PS_ELD4. Added the relevant functionality to GateState and fixed a NPE.

See merge request core-developers/forge!2271
2019-11-07 06:17:25 +00:00
Agetian
3b636be2fe Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master 2019-11-07 08:10:19 +03:00
Agetian
1af940b034 - Simpler adventure card detection. 2019-11-07 08:08:03 +03:00
Agetian
8cb1789e60 - Added puzzle PS_ELD4.
- Added OnAdventure functionality to game states.
- Fixed a NPE when dev-adding a card to exile.
2019-11-07 08:06:31 +03:00
Michael Kamensky
01782de3a6 Merge branch 'patch-7' into 'master'
Update de-DE.properties from forum (twosat user)

See merge request core-developers/forge!2270
2019-11-07 04:02:59 +00:00
Michael Kamensky
3a7e35c51d Merge branch 'cardtranslationforgecore' into 'master'
Moved CardTranslation to forge-core.

See merge request core-developers/forge!2253
2019-11-07 04:02:57 +00:00
swordshine
bff24a1d3d Merge branch 'patch-6' into 'master'
Replace sprite_icons.png in darkred skin

See merge request core-developers/forge!2269
2019-11-07 00:52:17 +00:00
swordshine
8a075000a2 Merge branch 'patch-5' into 'master'
Replace bg_splash.png in Darkred skin

See merge request core-developers/forge!2268
2019-11-07 00:52:05 +00:00
Churrufli
5e0761d085 Update de-DE.properties from forum (twosat user) 2019-11-06 23:33:57 +00:00
Churrufli
2184ddf1bb Replace sprite_icons.png in darkred skin 2019-11-06 17:54:06 +00:00
Churrufli
fd032f6ccd Replace bg_splash.png in Darkred skin 2019-11-06 17:52:30 +00:00
Hans Mackowiak
164c819523 Merge branch 'patch-5' into 'master'
Update dread_warlock: added Warlock type

See merge request core-developers/forge!2267
2019-11-06 15:06:41 +00:00
Hans Mackowiak
987043ead2 Update dread_warlock: added Warlock type 2019-11-06 15:06:41 +00:00
swordshine
4aa9c224d0 Merge branch 'pioneerrandomquestworld' into 'master'
Pioneer random quest mode

See merge request core-developers/forge!2266
2019-11-06 08:32:13 +00:00
swordshine
2ac6fa4542 Merge branch 'elddeckgendata' into 'master'
Updated Modern and Standard Deckgen Data

See merge request core-developers/forge!2265
2019-11-06 08:31:48 +00:00
swordshine
2797f95cd3 Merge branch 'master' into 'master'
update simplified chinese translation

See merge request core-developers/forge!2264
2019-11-06 08:31:33 +00:00
austinio7116
5eb9be6248 Pioneer random quest mode 2019-11-06 08:07:21 +00:00
austinio7116
23eebf9037 Updated Modern and Standard Deckgen Data 2019-11-06 06:17:41 +00:00
CCTV-1
b59adab68d added missing translation label 2019-11-06 10:45:44 +08:00
CCTV-1
b2d44105be update simplified chinese translation 2019-11-06 10:44:19 +08:00
swordshine
2235546f2a Merge branch 'translation10' into 'master'
More translations: New menu settings, Search, Priority, PayMana, Discard, Order, Exile, Delve, ...

See merge request core-developers/forge!2251
2019-11-06 01:04:53 +00:00
swordshine
59d104f68b Merge branch 'pioneerdeckgen' into 'master'
Pioneer deckgen

See merge request core-developers/forge!2263
2019-11-06 01:04:07 +00:00
swordshine
6abdfd391d Merge branch '1191-format-pioneer' into 'master'
Resolve "Format: pioneer"

Closes #1191

See merge request core-developers/forge!2250
2019-11-06 01:04:02 +00:00
Hans Mackowiak
a6ff0b5b10 Resolve "Format: pioneer" 2019-11-06 01:04:02 +00:00
austinio7116
02969cfe5b Pioneer archetype deck generation added to UI 2019-11-05 23:02:58 +00:00
austinio7116
c6a2c35850 Pioneer initial meta
(cherry picked from commit af89555)
2019-11-05 22:37:00 +00:00
maustin
4c0a71f37d Merge branch '1191-format-pioneer' of https://git.cardforge.org/core-developers/forge into pioneerdeckgen 2019-11-05 22:36:19 +00:00
Hans Mackowiak
74e3bd1895 Pionier: November 2019 Banned cards 2019-11-05 12:05:34 +00:00
Hans Mackowiak
121c9f5012 add PioneerPredicate to magicDb 2019-11-05 08:36:19 +00:00
Hans Mackowiak
7b1cd816b7 Update formats 2019-11-05 08:25:43 +00:00
Michael Kamensky
6a76cc8bc6 Merge branch 'newBranch' into 'master'
Update Network and Dependency files

See merge request core-developers/forge!2259
2019-11-04 16:44:48 +00:00
swordshine
9b9c38126e Merge branch '1200-memory-theft-allows-discarding-of-a-land' into 'master'
Resolve "Memory Theft allows discarding of a land"

Closes #1200

See merge request core-developers/forge!2261
2019-11-04 14:26:01 +00:00
Michael Kamensky
e4ee0c768f Merge branch 'ManaPoolHiddenCleanup' into 'master'
ManaPool: no hidden keyword there, so no need for extra cleanup

See merge request core-developers/forge!2262
2019-11-04 13:46:08 +00:00
Hans Mackowiak
340de153c8 ManaPool: no hidden keyword there, so no need for extra cleanup 2019-11-04 13:46:08 +00:00
Hans Mackowiak
2bf477102d Update memory_theft: add nonLand part 2019-11-04 09:42:30 +00:00
Anthony Calosa
42a15b40b3 Merge remote-tracking branch 'remotes/core/master' into newBranch 2019-11-04 07:47:27 +08:00
Michael Kamensky
a2589cd433 Merge branch 'patch-5' into 'master'
Update net-decks.txt adding Current Pioneer Decks

See merge request core-developers/forge!2260
2019-11-03 13:49:24 +00:00
Churrufli
7ec7025ed4 Update net-decks.txt adding Current Pioneer Decks 2019-11-03 07:40:41 +00:00
Anthony Calosa
5edeb6df94 removed config (log4j2 uses alternate config via xml) 2019-11-02 10:12:59 +08:00
Anthony Calosa
001a1981cf update log4j 1.2.17 -> log4j 2.11.2
(log4j 2.12.x latest needs higher Android API)
2019-11-02 10:07:13 +08:00
Anthony Calosa
446fb59473 some device are looking for this file so include it on storage
java.io.FileNotFoundException:
/storage/emulated/0/Forge/src/main/resources/log4jConfig.config
(No such file or directory)
2019-11-01 21:42:24 +08:00
Anthony Calosa
3b58d6df42 refactor rename 2019-11-01 17:46:14 +08:00
Anthony Calosa
a80c683901 support android 6 (slow networkplay) tested with two Android 6 device 2019-11-01 17:34:42 +08:00
Anthony Calosa
a5b65eaaed add old de/serialization for android 7.1 and below 2019-11-01 15:03:30 +08:00
Anthony Calosa
31182289b7 Update dependency and use custom de/encoder for netty 2019-11-01 12:41:30 +08:00
Anthony Calosa
85eb740264 aifixes NPE 2019-11-01 12:40:40 +08:00
Anthony Calosa
4318e23a40 support UST extended art 2019-11-01 12:39:57 +08:00
Hans Mackowiak
4ca7352d5c Merge branch 'fix-staticabilitycanttarget' into 'master'
Fix StaticAbilityCantTarget check not calling the common routine.

See merge request core-developers/forge!2258
2019-10-29 19:46:37 +00:00
Michael Kamensky
84905bd726 Fix StaticAbilityCantTarget check not calling the common routine. 2019-10-29 19:46:37 +00:00
swordshine
18e16368be Merge branch 'master' into 'master'
Added puzzle PS_ELD3 - Possibility Storm - Throne of Eldraine 03

See merge request core-developers/forge!2257
2019-10-29 07:51:49 +00:00
Agetian
7ed84c4c3f - Added puzzle PS_ELD3. 2019-10-29 09:34:39 +03:00
Michael Kamensky
f334211395 Merge branch 'newBranch' into 'master'
Mobile Forge: Card Sleeves & Round Border Refactor

See merge request core-developers/forge!2255
2019-10-26 14:46:09 +00:00
Anthony Calosa
42f4126aff Prepare Sleeve for Desktop... 2019-10-25 15:37:13 +08:00
Anthony Calosa
5790e29daa Merge remote-tracking branch 'remotes/core/master' into newBranch 2019-10-25 14:22:07 +08:00
Anthony Calosa
88a4a2c6cf Update 2019-10-25 14:21:25 +08:00
swordshine
90b72fc11e Merge branch '1194-resolute-rider-has-incorrect-activated-ability-cost' into 'master'
Resolve "Resolute Rider has incorrect activated ability cost"

Closes #1194

See merge request core-developers/forge!2256
2019-10-24 08:46:54 +00:00
Hans Mackowiak
00391df1f0 Fix Resolute Rider 2019-10-24 07:46:00 +00:00
swordshine
15de0c0bba Merge branch 'master' into 'master'
Added puzzle PS_ELD2 - Possibility Storm - Throne of Eldraine 02

See merge request core-developers/forge!2254
2019-10-24 01:00:06 +00:00
Agetian
a2fdce9be9 - Added puzzle PS_ELD2. 2019-10-23 22:42:52 +03:00
klaxnek
fa6fce9589 Moved CardTranslation to forge-core. We need it to translate card info in forge-game. 2019-10-23 11:47:21 +02:00
Anthony Calosa
cc1f03fc94 Update 2019-10-23 12:57:36 +08:00
Anthony Calosa
8b25f6f129 Fix Foil Overlay when Round Border is enabled 2019-10-23 11:57:56 +08:00
Anthony Calosa
b37937421c Merge remote-tracking branch 'remotes/core/master' into newBranch 2019-10-23 11:52:52 +08:00
klaxnek
4c3e4f2170 Translate PlayerControllerHuman.java. Discard, order of cards, delve, exile. 2019-10-22 13:23:57 +02:00
klaxnek
dc12c50c1a Forgot lblCleanupPhase 2019-10-22 12:56:53 +02:00
klaxnek
2b986f5bac Translate PlayerControllerHuman.java. Discard cards 2019-10-22 12:50:15 +02:00
klaxnek
2accf7543e Translated Input Pay Mana. Expanded sentences in order to translate them. 2019-10-22 12:08:28 +02:00
klaxnek
ba37189410 Translate InputPassPriority.java 2019-10-22 11:36:36 +02:00
Hans Mackowiak
d578eee402 Add Pioneer Format 2019-10-22 08:47:00 +00:00
Hans Mackowiak
5107d89ef5 GameFormat: add Píoneer 2019-10-22 08:45:07 +00:00
Hans Mackowiak
72b8b5c98e Update StaticData: are the Predicates even used? 2019-10-22 08:42:12 +00:00
Anthony Calosa
402885391f Card Sleeves 2019-10-22 16:12:03 +08:00
klaxnek
b4d153ab3b Translate Search word in search filters 2019-10-22 09:51:03 +02:00
klaxnek
1a0cb62ac8 Translate Preload Extended Art 2019-10-22 09:42:16 +02:00
klaxnek
c0baf70c59 Translate new Settings menú 2019-10-22 09:37:05 +02:00
swordshine
166cf2623c Merge branch 'master' into 'master'
update Simplified Chinese translation

See merge request core-developers/forge!2249
2019-10-22 06:02:17 +00:00
CCTV-1
8567b69073 update 'Draft, Gauntlet and Puzzle Screens' Simplified Chinese translation 2019-10-22 13:01:37 +08:00
swordshine
3a4271e66d Merge branch 'updatetranslation' into 'master'
update new settings translation

See merge request core-developers/forge!2246
2019-10-22 03:05:25 +00:00
swordshine
8b723aebd9 Merge branch 'master' into 'master'
update BitmapFontWriter.java to latest version

See merge request core-developers/forge!2243
2019-10-22 03:05:18 +00:00
swordshine
eb59d6c86b Merge branch 'translation09' into 'master'
Mobile: Translate Draft, Gauntlet and Puzzle Screens.

See merge request core-developers/forge!2228
2019-10-22 03:05:04 +00:00
Sol
a291b75dd9 Merge branch 'patch-4' into 'master'
Questing Beast: fix Damage Trigger

See merge request core-developers/forge!2247
2019-10-22 00:41:25 +00:00
Hans Mackowiak
f1a76e1e76 Questing Beast: fix Damage Trigger 2019-10-22 00:41:25 +00:00
Sol
a391f7414f Merge branch 'updateBanList' into 'master'
BanList 2019-10-21

See merge request core-developers/forge!2248
2019-10-22 00:40:49 +00:00
Hans Mackowiak
299de54ba5 BanList 2019-10-21 2019-10-22 00:40:49 +00:00
CCTV-1
37bae14dfd update Simplified Chinese characters list 2019-10-19 10:26:43 +08:00
CCTV-1
d780aa43d4 update new settings translation 2019-10-18 18:30:59 +08:00
CCTV-1
91534776d1 convert \t to four spaces,makes the diff able to read 2019-10-17 11:07:00 +08:00
CCTV-1
31bb611ecf update BitmapFontWriter.java to latest version(https://github.com/libgdx/libgdx/blob/master/extensions/gdx-tools/src/com/badlogic/gdx/tools/bmfont/BitmapFontWriter.java) 2019-10-15 18:35:16 +08:00
Peter
53c88f0302 Added to contributors XD 2019-10-07 22:40:45 +02:00
Peter
ad1cc78578 Mobile: Translate Draft, Gauntlet and Puzzle Screens.
Fixed crash in LoadGameMenu (wrong translated enum...)
2019-10-07 22:39:00 +02:00
395 changed files with 2487 additions and 1348 deletions

View File

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

View File

@@ -1281,7 +1281,8 @@ public class AiBlockController {
oppCreatureCount = ComputerUtil.countUsefulCreatures(attackersLeft.get(0).getController());
}
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
if (attacker != null && attacker.getOwner() != null)
if (attacker.getOwner().equals(ai) && "6".equals(attacker.getSVar("SacMe"))) {
// Temporarily controlled object - don't trade with it
// TODO: find a more reliable way to figure out that control will be reestablished next turn
return false;

View File

@@ -2779,7 +2779,7 @@ public class ComputerUtil {
// Iceberg does use Ice as Storage
|| (type == CounterType.ICE && !"Iceberg".equals(c.getName()))
// some lands does use Depletion as Storage Counter
|| (type == CounterType.DEPLETION && !c.canUntapPhaseController())
|| (type == CounterType.DEPLETION && c.hasKeyword("CARDNAME doesn't untap during your untap step."))
// treat Time Counters on suspended Cards as Bad,
// and also on Chronozoa
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))

View File

@@ -1742,7 +1742,7 @@ public class ComputerUtilCard {
if (!c.isCreature()) {
return false;
}
if (c.hasKeyword("CARDNAME can't attack or block.") || (!c.canUntapPhaseController() && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
if (c.hasKeyword("CARDNAME can't attack or block.") || (c.hasKeyword("CARDNAME doesn't untap during your untap step.") && c.isTapped()) || (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
return true;
}
return false;

View File

@@ -173,7 +173,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
}
if (!c.canUntapPhaseController()) {
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
if (c.isTapped()) {
value = addValue(50 + (c.getCMC() * 5), "tapped-useless"); // reset everything - useless
} else {

View File

@@ -11,10 +11,7 @@ import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CounterType;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
@@ -24,6 +21,7 @@ import forge.game.mana.ManaPool;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.ability.AbilityKey;
import forge.game.trigger.TriggerType;
@@ -362,6 +360,12 @@ public abstract class GameState {
if (c.isFaceDown()) {
newText.append("|FaceDown"); // Exiled face down
}
if (c.isAdventureCard() && c.getZone().is(ZoneType.Exile)) {
// TODO: this will basically default all exiled cards with Adventure to being "On Adventure".
// Need to figure out a better way to detect if it's actually on adventure.
newText.append("|OnAdventure");
}
}
if (zoneType == ZoneType.Battlefield || zoneType == ZoneType.Exile) {
@@ -1202,6 +1206,16 @@ public abstract class GameState {
c.setState(CardStateName.Flipped, true);
} else if (info.startsWith("Meld")) {
c.setState(CardStateName.Meld, true);
} else if (info.startsWith("OnAdventure")) {
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c);
StringBuilder sbPlay = new StringBuilder();
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");
saAdventure.setSVar("Play", sbPlay.toString());
saAdventure.setActivatingPlayer(c.getOwner());
saAdventure.resolve();
c.setExiledWith(c); // This seems to be the way it's set up internally. Potentially not needed here?
} else if (info.startsWith("IsCommander")) {
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
c.setCommander(true);

View File

@@ -33,7 +33,6 @@ public enum SpellApiToAi {
.put(ApiType.BidLife, BidLifeAi.class)
.put(ApiType.Bond, BondAi.class)
.put(ApiType.Branch, AlwaysPlayAi.class)
.put(ApiType.CantUntapTurn, CantUntapTurnAi.class)
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
.put(ApiType.ChangeX, AlwaysPlayAi.class)

View File

@@ -1146,7 +1146,6 @@ public class AttachAi extends SpellAbilityAi {
final List<String> keywords = new ArrayList<>();
boolean grantingAbilities = false;
boolean grantingExtraBlock = false;
boolean grantingCantUntap = false;
for (final StaticAbility stAbility : attachSource.getStaticAbilities()) {
final Map<String, String> stabMap = stAbility.getMapParams();
@@ -1166,7 +1165,6 @@ public class AttachAi extends SpellAbilityAi {
grantingAbilities |= stabMap.containsKey("AddAbility");
grantingExtraBlock |= stabMap.containsKey("CanBlockAmount") || stabMap.containsKey("CanBlockAny");
grantingCantUntap |= stabMap.containsKey("CantUntap");
String kws = stabMap.get("AddKeyword");
if (kws != null) {
@@ -1199,7 +1197,6 @@ public class AttachAi extends SpellAbilityAi {
if (totToughness + totPower < 4 && (!keywords.isEmpty() || grantingExtraBlock)) {
final int pow = totPower;
final boolean extraBlock = grantingExtraBlock;
final boolean cantUntap = grantingCantUntap;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
@@ -1218,9 +1215,6 @@ public class AttachAi extends SpellAbilityAi {
if (extraBlock && CombatUtil.canBlock(c, true) && !c.canBlockAny()) {
return true;
}
if (cantUntap && c.isTapped()) {
return true;
}
return false;
}
});
@@ -1638,6 +1632,8 @@ public class AttachAi extends SpellAbilityAi {
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
return !card.isUntapped();
}
return true;
}

View File

@@ -1,64 +0,0 @@
package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.cost.CostPutCounter;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class CantUntapTurnAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
if (sa.usesTargeting()) {
CardCollection oppCards = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
CardCollection relevantToHold = CardLists.filter(oppCards,
Predicates.and(CardPredicates.Presets.TAPPED, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
if (card.isCreature()) {
return true;
}
for (final SpellAbility ab : card.getSpellAbilities()) {
if (ab.isAbility() && (ab.getPayCosts() != null) && ab.getPayCosts().hasTapCost()) {
return true;
}
}
return false;
}
}));
Card bestToTap = ComputerUtilCard.getBestAI(relevantToHold);
Card validTarget = ComputerUtilCard.getBestAI(CardLists.filter(oppCards, CardPredicates.Presets.TAPPED));
if (validTarget == null) {
validTarget = ComputerUtilCard.getBestAI(oppCards);
}
if (bestToTap != null) {
sa.getTargets().add(bestToTap);
return true;
} else if (sa.hasParam("Planeswalker")
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)) {
sa.getTargets().add(validTarget);
return true;
}
return false;
}
return true;
}
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return mandatory || canPlayAI(aiPlayer, sa);
}
}

View File

@@ -45,6 +45,8 @@ public class FightAi extends SpellAbilityAi {
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
List<Card> humCreatures = ai.getOpponents().getCreaturesInPlay();
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
if (humCreatures.isEmpty())
return false; //prevent IndexOutOfBoundsException on MOJHOSTO variant
// assumes the triggered card belongs to the ai
if (sa.hasParam("Defined")) {

View File

@@ -15,6 +15,7 @@ import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.phase.Untap;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -129,6 +130,9 @@ public abstract class PumpAiBase extends SpellAbilityAi {
// target needs to be a creature, controlled by the player which is attacked
return !sa.getHostCard().isTapped() || (combat != null && combat.isAttacking(sa.getHostCard())
&& card.getController().equals(combat.getDefenderPlayerByAttacker(sa.getHostCard())));
} else if (keyword.endsWith("This card doesn't untap during your next untap step.")) {
return !ph.getPhase().isBefore(PhaseType.MAIN2) && !card.isUntapped() && ph.isPlayerTurn(ai)
&& Untap.canUntap(card);
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) {
if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card))
@@ -507,7 +511,8 @@ public abstract class PumpAiBase extends SpellAbilityAi {
for (final String keyword : keywords) {
// since most keywords are combat relevant check for those that are
// not
if (keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
if (keyword.endsWith("This card doesn't untap during your next untap step.")
|| keyword.endsWith("Shroud") || keyword.endsWith("Hexproof")) {
return true;
}
}

View File

@@ -63,6 +63,7 @@ public class SacrificeAi extends SpellAbilityAi {
final boolean destroy = sa.hasParam("Destroy");
Player opp = ai.getWeakestOpponent();
if (tgt != null) {
sa.resetTargets();
if (!opp.canBeTargetedBy(sa)) {
@@ -74,8 +75,16 @@ public class SacrificeAi extends SpellAbilityAi {
num = (num == null) ? "1" : num;
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), num, sa);
List<Card> list =
CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
List<Card> list = null;
try {
list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa);
} catch (NullPointerException e) {
return false;
} finally {
if (list == null)
return false;
}//prevent NPE on MoJhoSto
for (Card c : list) {
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
return false;

View File

@@ -331,7 +331,7 @@ public class UntapAi extends SpellAbilityAi {
}
// See if there's anything to untap that is tapped and that doesn't untap during the next untap step by itself
CardCollection noAutoUntap = CardLists.filter(untapList, Predicates.not(CardPredicates.canUntapPhaseController()));
CardCollection noAutoUntap = CardLists.filter(untapList, CardPredicates.hasKeyword("CARDNAME doesn't untap during your untap step."));
if (!noAutoUntap.isEmpty()) {
return ComputerUtilCard.getBestAI(noAutoUntap);
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-core</artifactId>
@@ -21,7 +21,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<version>3.8.1</version>
</dependency>
</dependencies>

View File

@@ -11,6 +11,7 @@ import org.apache.commons.lang3.StringUtils;
public abstract class LobbyPlayer {
protected String name;
private int avatarIndex = -1;
private int sleeveIndex = -1;
private String avatarCardImageKey;
public LobbyPlayer(String name) {
@@ -59,9 +60,15 @@ public abstract class LobbyPlayer {
public int getAvatarIndex() {
return avatarIndex;
}
public int getSleeveIndex() {
return sleeveIndex;
}
public void setAvatarIndex(int avatarIndex) {
this.avatarIndex = avatarIndex;
}
public void setSleeveIndex(int sleeveIndex) {
this.sleeveIndex = sleeveIndex;
}
public String getAvatarCardImageKey() {
return avatarCardImageKey;

View File

@@ -35,6 +35,7 @@ public class StaticData {
private Predicate<PaperCard> standardPredicate;
private Predicate<PaperCard> brawlPredicate;
private Predicate<PaperCard> pioneerPredicate;
private Predicate<PaperCard> modernPredicate;
private Predicate<PaperCard> commanderPredicate;
private Predicate<PaperCard> oathbreakerPredicate;
@@ -197,13 +198,13 @@ public class StaticData {
public TokenDb getAllTokens() { return allTokens; }
public Predicate<PaperCard> getStandardPredicate() {
return standardPredicate;
}
public void setStandardPredicate(Predicate<PaperCard> standardPredicate) { this.standardPredicate = standardPredicate; }
public void setModernPredicate(Predicate<PaperCard> modernPredicate) { this.modernPredicate = standardPredicate; }
public void setPioneerPredicate(Predicate<PaperCard> pioneerPredicate) { this.pioneerPredicate = pioneerPredicate; }
public void setModernPredicate(Predicate<PaperCard> modernPredicate) { this.modernPredicate = modernPredicate; }
public void setCommanderPredicate(Predicate<PaperCard> commanderPredicate) { this.commanderPredicate = commanderPredicate; }
@@ -211,9 +212,11 @@ public class StaticData {
public void setBrawlPredicate(Predicate<PaperCard> brawlPredicate) { this.brawlPredicate = brawlPredicate; }
public Predicate<PaperCard> getModernPredicate() {
return modernPredicate;
}
public Predicate<PaperCard> getStandardPredicate() { return standardPredicate; }
public Predicate<PaperCard> getPioneerPredicate() { return pioneerPredicate; }
public Predicate<PaperCard> getModernPredicate() { return modernPredicate; }
public Predicate<PaperCard> getCommanderPredicate() { return commanderPredicate; }

View File

@@ -1,9 +1,6 @@
package forge.card;
package forge.util;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Charsets;
import forge.properties.ForgeConstants;
import forge.util.LineReader;
import java.io.FileInputStream;
import java.io.IOException;
@@ -15,12 +12,12 @@ public class CardTranslation {
private static Map <String, String> translatednames;
private static Map <String, String> translatedtypes;
private static Map <String, String> translatedoracles;
private static String languageSelected;
private static String languageSelected = "en-US";
private static void readTranslationFile(String language) {
private static void readTranslationFile(String language, String languagesDirectory) {
String filename = "cardnames-" + language + ".txt";
try (LineReader translationFile = new LineReader(new FileInputStream(ForgeConstants.LANG_DIR + filename), Charsets.UTF_8)) {
try (LineReader translationFile = new LineReader(new FileInputStream(languagesDirectory + filename), Charsets.UTF_8)) {
for (String line : translationFile.readLines()) {
String[] matches = line.split("\\|");
if (matches.length >= 2) {
@@ -34,7 +31,7 @@ public class CardTranslation {
}
}
} catch (IOException e) {
Log.error("Error reading translation file: cardnames-" + language + ".txt");
System.err.println("Error reading translation file: cardnames-" + language + ".txt");
}
}
@@ -66,7 +63,7 @@ public class CardTranslation {
}
public static HashMap<String, String> getTranslationTexts(String cardname, String altcardname) {
HashMap<String, String> translations = new HashMap<String, String>();
HashMap<String, String> translations = new HashMap<>();
translations.put("name", getTranslatedName(cardname));
translations.put("oracle", getTranslatedOracle(cardname));
translations.put("altname", getTranslatedName(altcardname));
@@ -78,14 +75,14 @@ public class CardTranslation {
return !languageSelected.equals("en-US");
}
public static void preloadTranslation(String language) {
public static void preloadTranslation(String language, String languagesDirectory) {
languageSelected = language;
if (needsTranslation()) {
translatednames = new HashMap<>();
translatedtypes = new HashMap<>();
translatedoracles = new HashMap<>();
readTranslationFile(languageSelected);
readTranslationFile(languageSelected, languagesDirectory);
}
}
}

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-game</artifactId>
@@ -32,8 +32,8 @@
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-log4j</artifactId>
<version>1.7.5</version>
<artifactId>sentry-log4j2</artifactId>
<version>1.7.27</version>
</dependency>
</dependencies>

View File

@@ -701,7 +701,9 @@ public class GameAction {
// Run triggers
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(c);
runParams.put(AbilityKey.Cause, cause);
runParams.put(AbilityKey.Origin, origin.getZoneType().name());
if (origin != null) { // is generally null when adding via dev mode
runParams.put(AbilityKey.Origin, origin.getZoneType().name());
}
if (params != null) {
runParams.putAll(params);
}

View File

@@ -47,7 +47,7 @@ import java.util.Map.Entry;
public class GameFormat implements Comparable<GameFormat> {
private final String name;
public enum FormatType {Sanctioned, Casual, Historic, Digital, Custom}
public enum FormatSubType {Block, Standard, Extended, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Custom}
public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Custom}
// contains allowed sets, when empty allows all sets
private FormatType formatType;
@@ -290,6 +290,7 @@ public class GameFormat implements Comparable<GameFormat> {
private List<String> coreFormats = new ArrayList<>();
{
coreFormats.add("Standard.txt");
coreFormats.add("Pioneer.txt");
coreFormats.add("Modern.txt");
coreFormats.add("Legacy.txt");
coreFormats.add("Vintage.txt");
@@ -468,6 +469,10 @@ public class GameFormat implements Comparable<GameFormat> {
return this.map.get("Extended");
}
public GameFormat getPioneer() {
return this.map.get("Pioneer");
}
public GameFormat getModern() {
return this.map.get("Modern");
}

View File

@@ -324,12 +324,6 @@ public class StaticEffect {
if (hasParam("CanBlockAmount")) {
affectedCard.removeCanBlockAdditional(getTimestamp());
}
if (hasParam("CantUntap")) {
affectedCard.removeCantUntap(getTimestamp());
}
if (hasParam("CantUntapPlayer")) {
affectedCard.removeCantUntapPlayer(getTimestamp());
}
affectedCard.updateAbilityTextForView(); // only update keywords and text for view to avoid flickering
}

View File

@@ -30,7 +30,6 @@ public enum ApiType {
Block (BlockEffect.class),
Bond (BondEffect.class),
Branch (BranchEffect.class),
CantUntapTurn (CantUntapTurnEffect.class),
ChangeCombatants (ChangeCombatantsEffect.class),
ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class),

View File

@@ -1,33 +0,0 @@
package forge.game.ability.effects;
import java.util.List;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
public class CantUntapTurnEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card host = sa.getHostCard();
final Game game = host.getGame();
final long timestamp = game.getNextTimestamp();
final int n = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Turns", "1"), sa);
List<Card> cards = getTargetCards(sa);
for (final Card tgtC : cards) {
if (sa.usesTargeting() && !tgtC.canBeTargetedBy(sa)) {
continue;
}
tgtC.addCantUntapTurn(timestamp, n);
}
}
}

View File

@@ -48,15 +48,6 @@ public class EffectEffect extends SpellAbilityEffect {
String effectImprinted = null;
List<Player> effectOwner = null;
boolean imprintOnHost = false;
final String duration = sa.getParam("Duration");
// special for until lose control or host leaves play
if ("UntilLoseControlOfHost".equals(duration) || "UntilHostLeavesPlay".equals(duration)
|| "UntilUntaps".equals(duration)) {
if (!hostCard.isInPlay()) {
return;
}
}
if (sa.hasParam("Abilities")) {
effectAbilities = sa.getParam("Abilities").split(",");
@@ -247,6 +238,7 @@ public class EffectEffect extends SpellAbilityEffect {
}
// Duration
final String duration = sa.getParam("Duration");
if ((duration == null) || !duration.equals("Permanent")) {
final GameCommand endEffect = new GameCommand() {
private static final long serialVersionUID = -5861759814760561373L;
@@ -263,18 +255,10 @@ public class EffectEffect extends SpellAbilityEffect {
else if (duration.equals("UntilHostLeavesPlay")) {
hostCard.addLeavesPlayCommand(endEffect);
}
else if (duration.equals("UntilLoseControlOfHost")) {
hostCard.addLeavesPlayCommand(endEffect);
hostCard.addChangeControllerCommand(endEffect);
}
else if (duration.equals("HostLeavesOrEOT")) {
game.getEndOfTurn().addUntil(endEffect);
hostCard.addLeavesPlayCommand(endEffect);
}
else if (duration.equals("UntilUntaps")) {
hostCard.addLeavesPlayCommand(endEffect);
hostCard.addUntapCommand(endEffect);
}
else if (duration.equals("UntilYourNextTurn")) {
game.getCleanup().addUntil(controller, endEffect);
}

View File

@@ -280,9 +280,6 @@ public class Card extends GameEntity implements Comparable<Card> {
private final Table<SpellAbility, StaticAbility, Integer> numberTurnActivationsStatic = HashBasedTable.create();
private final Table<SpellAbility, StaticAbility, Integer> numberGameActivationsStatic = HashBasedTable.create();
private final Map<Long, Integer> cantUntapTurns = Maps.newTreeMap();
private final Set<Long> cantUntap = Sets.newHashSet();
private final Map<Long, Player> cantUntapPlayer = Maps.newTreeMap();
// Enumeration for CMC request types
public enum SplitCMCMode {
@@ -3609,94 +3606,6 @@ public class Card extends GameEntity implements Comparable<Card> {
getGame().fireEvent(new GameEventCardTapped(this, true));
}
public final boolean canUntapPhase(Player activePlayer) {
if (activePlayer.equals(getController())) {
return canUntapPhaseController();
}
if (cantUntapPlayer.containsValue(activePlayer)) {
return false;
}
return !isExertedBy(activePlayer);
}
public final boolean canUntapPhaseController() {
if (!cantUntap.isEmpty() || !cantUntapTurns.isEmpty()) {
return false;
}
Player p = getController();
if (cantUntapPlayer.containsValue(p)) {
return false;
}
return !isExertedBy(p);
}
public boolean isCantUntap() {
return !cantUntap.isEmpty();
}
public final boolean addCantUntap(final long timestamp) {
boolean result = cantUntap.add(timestamp);
getView().updateCantUntap(this);
return result;
}
public final boolean removeCantUntap(final long timestamp) {
boolean result = cantUntap.remove(timestamp);
getView().updateCantUntap(this);
return result;
}
public final void addCantUntapTurn(final long timestamp, final int value) {
cantUntapTurns.put(timestamp, value);
getView().updateCantUntap(this);
}
public final void removeCantUntapTurn(final long timestamp) {
cantUntapTurns.remove(timestamp);
getView().updateCantUntap(this);
}
public final void removeCantUntapTurn() {
// reduce by one each turn
List<Long> toRemove = Lists.newArrayList();
for (final Map.Entry<Long, Integer> e : cantUntapTurns.entrySet()) {
e.setValue(e.getValue() - 1);
if (e.getValue() <= 0) {
toRemove.add(e.getKey());
}
}
for (final long l : toRemove) {
cantUntapTurns.remove(l);
}
getView().updateCantUntap(this);
}
public final int getCantUntapTurnValue() {
if (cantUntapTurns.isEmpty()) {
return 0;
}
return Collections.max(cantUntapTurns.values());
}
public final void addCantUntapPlayer(final Player p, final long timestamp) {
cantUntapPlayer.put(timestamp, p);
getView().updateCantUntap(this);
}
public final void removeCantUntapPlayer(final long timestamp) {
cantUntapPlayer.remove(timestamp);
getView().updateCantUntap(this);
}
public final Collection<Player> getCantUntapPlayer() {
return cantUntapPlayer.values();
}
public final void untap() {
if (!tapped) { return; }

View File

@@ -67,15 +67,6 @@ public final class CardPredicates {
};
}
public static final Predicate<Card> canUntapPhaseController() {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canUntapPhaseController();
}
};
}
public static final Predicate<Card> isType(final String cardType) {
return new Predicate<Card>() {
@Override

View File

@@ -2,8 +2,6 @@ package forge.game.card;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.ImageKeys;
import forge.card.*;
import forge.card.mana.ManaCost;
@@ -24,7 +22,6 @@ import forge.util.Lang;
import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
@@ -607,7 +604,7 @@ public class CardView extends GameEntityView {
sb.append("\r\n\r\n").append("(").append(taltname).append(") ").append(taltoracle);
}
String nonAbilityText = getNonAbilityText();
String nonAbilityText = get(TrackableProperty.NonAbilityText);
if (!nonAbilityText.isEmpty()) {
sb.append("\r\n \r\nNon ability features: \r\n");
sb.append(nonAbilityText.replaceAll("CARDNAME", getName()));
@@ -635,6 +632,22 @@ public class CardView extends GameEntityView {
sb.append("\r\n");
}
if (getCanBlockAny()) {
sb.append("\r\n\r\n");
sb.append("CARDNAME can block any number of creatures.".replaceAll("CARDNAME", getName()));
sb.append("\r\n");
} else {
int i = getBlockAdditional();
if (i > 0) {
sb.append("\r\n\r\n");
sb.append("CARDNAME can block an additional ".replaceAll("CARDNAME", getName()));
sb.append(i == 1 ? "creature" : Lang.nounWithNumeral(i, "creature"));
sb.append(" each combat.");
sb.append("\r\n");
}
}
String cloner = get(TrackableProperty.Cloner);
if (!cloner.isEmpty()) {
sb.append("\r\nCloned by: ").append(cloner);
@@ -643,57 +656,6 @@ public class CardView extends GameEntityView {
return sb.toString().trim();
}
public String getNonAbilityText() {
StringBuilder sb = new StringBuilder(get(TrackableProperty.NonAbilityText));
if (getCanBlockAny()) {
sb.append("\r\n");
sb.append("CARDNAME can block any number of creatures.");
sb.append("\r\n");
} else {
int i = getBlockAdditional();
if (i > 0) {
sb.append("\r\n");
sb.append("CARDNAME can block an additional ");
sb.append(i == 1 ? "creature" : Lang.nounWithNumeral(i, "creature"));
sb.append(" each combat.");
sb.append("\r\n");
}
}
if (getCantUntap()) {
String msg = "CARDNAME doesnt untap during its controllers untap step.";
sb.append("\r\n");
sb.append(msg);
sb.append("\r\n");
} else {
int i = this.getCantUntapTurn();
if (i == 1) {
String msg = "CARDNAME doesnt untap during its controllers next untap step.";
sb.append("\r\n");
sb.append(msg);
sb.append("\r\n");
} else if (i > 1) {
String str = Lang.nounWithNumeral(i, "untap step");
String msg = ("CARDNAME doesnt untap during its controllers next " + str + ".");
sb.append("\r\n");
sb.append(msg);
sb.append("\r\n");
}
}
FCollectionView<PlayerView> untapList = getCantUntapPlayer();
if (untapList != null && !untapList.isEmpty()) {
String p = Lang.joinHomogenous(Lists.newArrayList(untapList), null, "or");
String msg = "CARDNAME doesnt untap during " + p + " untap step.";
sb.append("\r\n");
sb.append(msg);
sb.append("\r\n");
}
return sb.toString();
}
public CardStateView getCurrentState() {
return get(TrackableProperty.CurrentState);
}
@@ -805,39 +767,6 @@ public class CardView extends GameEntityView {
set(TrackableProperty.BlockAny, c.canBlockAny());
}
boolean getCantUntap() {
return get(TrackableProperty.CantUntapAny);
}
int getCantUntapTurn() {
return get(TrackableProperty.CantUntapTurns);
}
FCollectionView<PlayerView> getCantUntapPlayer() {
return get(TrackableProperty.CantUntapPlayer);
}
void updateCantUntap(Card c) {
set(TrackableProperty.CantUntapAny, c.isCantUntap());
set(TrackableProperty.CantUntapTurns, c.getCantUntapTurnValue());
Collection<Player> list = c.getCantUntapPlayer();
if (list.isEmpty()) {
set(TrackableProperty.CantUntapPlayer, null);
} else {
TrackableCollection<PlayerView> prop = get(TrackableProperty.CantUntapPlayer);
if (prop == null) {
prop = new TrackableCollection<>();
} else {
prop.clear();
}
for (Player p : list) {
prop.add(p.getView());
}
set(TrackableProperty.CantUntapPlayer, prop);
}
}
@Override
public String toString() {
String name = getName();

View File

@@ -234,7 +234,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
final Card host = sa.getHostCard();
if (mana.addsKeywords(sa) && mana.addsKeywordsType()
&& host.getType().hasStringType(mana.getManaAbility().getAddsKeywordsType())) {
final long timestamp = sa.getHostCard().getGame().getNextTimestamp();
final long timestamp = host.getGame().getNextTimestamp();
final List<String> kws = Arrays.asList(mana.getAddedKeywords().split(" & "));
host.addChangedCardKeywords(kws, null, false, false, timestamp);
if (mana.addsKeywordsUntil()) {
@@ -243,14 +243,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
@Override
public void run() {
if (!kws.isEmpty()) {
for (String kw : kws) {
if (kw.startsWith("HIDDEN")) {
sa.getHostCard().removeHiddenExtrinsicKeyword(kw);
}
}
host.removeChangedCardKeywords(timestamp);
}
host.removeChangedCardKeywords(timestamp);
host.getGame().fireEvent(new GameEventCardStatsChanged(host));
}
};
@@ -261,10 +254,10 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
}
}
if (mana.addsCounters(sa)) {
mana.getManaAbility().createETBCounters(sa.getHostCard());
mana.getManaAbility().createETBCounters(host);
}
if (mana.triggersWhenSpent()) {
mana.getManaAbility().addTriggersWhenSpent(sa, sa.getHostCard());
mana.getManaAbility().addTriggersWhenSpent(sa, host);
}
}
return true;

View File

@@ -44,7 +44,16 @@ public enum PhaseType {
nameForScripts = name_for_scripts;
}
public final boolean phaseforUpdateField() {
boolean result =
((ALL_PHASES.indexOf(this) >= ALL_PHASES.indexOf(UNTAP)
&& ALL_PHASES.indexOf(this) < ALL_PHASES.indexOf(COMBAT_FIRST_STRIKE_DAMAGE))
|| (ALL_PHASES.indexOf(this) >= ALL_PHASES.indexOf(MAIN2)
&& ALL_PHASES.indexOf(this) < ALL_PHASES.indexOf(CLEANUP)));
return result;
}
public final boolean isAfter(final PhaseType phase) {
return ALL_PHASES.indexOf(this) > ALL_PHASES.indexOf(phase);
}

View File

@@ -65,7 +65,7 @@ public class Untap extends Phase {
*/
@Override
public void executeAt() {
super.executeAt();
this.execute(this.at);
final Player turn = game.getPhaseHandler().getPlayerTurn();
Untap.doPhasing(turn);
@@ -83,9 +83,17 @@ public class Untap extends Phase {
* @return a boolean.
*/
public static boolean canUntap(final Card c) {
if (c.hasKeyword("CARDNAME doesn't untap during your untap step.")
|| c.hasKeyword("This card doesn't untap during your next untap step.")
|| c.hasKeyword("This card doesn't untap during your next two untap steps.")
|| c.hasKeyword("This card doesn't untap.")) {
return false;
}
//exerted need current player turn
final Player playerTurn = c.getGame().getPhaseHandler().getPlayerTurn();
return c.canUntapPhase(playerTurn);
return !c.isExertedBy(playerTurn);
}
public static final Predicate<Card> CANUNTAP = new Predicate<Card>() {
@@ -153,10 +161,11 @@ public class Untap extends Phase {
// other players untapping during your untap phase
List<Card> cardsWithKW = CardLists.getKeyword(game.getCardsIn(ZoneType.Battlefield),
"CARDNAME untaps during each other player's untap step.");
cardsWithKW = CardLists.getNotKeyword(cardsWithKW, "This card doesn't untap.");
cardsWithKW = CardLists.filterControlledBy(cardsWithKW, player.getAllOtherPlayers());
for (final Card cardWithKW : cardsWithKW) {
if (!cardWithKW.canUntapPhase(player)) {
if (cardWithKW.isExertedBy(player)) {
continue;
}
cardWithKW.untap();
@@ -193,7 +202,11 @@ public class Untap extends Phase {
// Remove temporary keywords
for (final Card c : player.getCardsIn(ZoneType.Battlefield)) {
c.removeCantUntapTurn();
c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next untap step.");
if (c.hasKeyword("This card doesn't untap during your next two untap steps.")) {
c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next two untap steps.");
c.addHiddenExtrinsicKeyword("This card doesn't untap during your next untap step.");
}
}
// remove exerted flags from all things in play

View File

@@ -2430,6 +2430,7 @@ public class Player extends GameEntity implements Comparable<Player> {
controllerCreator = ctrlr;
controller = ctrlr;
updateAvatar();
updateSleeve();
view.updateIsAI(this);
view.updateLobbyPlayerName(this);
}
@@ -2439,6 +2440,10 @@ public class Player extends GameEntity implements Comparable<Player> {
view.updateAvatarCardImageKey(this);
}
public void updateSleeve() {
view.updateSleeveIndex(this);
}
/**
* Run a procedure using a different controller
*/

View File

@@ -85,6 +85,13 @@ public class PlayerView extends GameEntityView {
set(TrackableProperty.AvatarCardImageKey, p.getLobbyPlayer().getAvatarCardImageKey());
}
public int getSleeveIndex() {
return get(TrackableProperty.SleeveIndex);
}
void updateSleeveIndex(Player p) {
set(TrackableProperty.SleeveIndex, p.getLobbyPlayer().getSleeveIndex());
}
public String getCurrentPlaneName() { return get(TrackableProperty.CurrentPlane); }
void updateCurrentPlaneName( String plane ) {
set(TrackableProperty.CurrentPlane, plane);

View File

@@ -188,9 +188,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
layers.add(StaticAbilityLayer.RULES);
}
if (hasParam("IgnoreEffectCost") || hasParam("Goad")
|| hasParam("CanBlockAny") || hasParam("CanBlockAmount")
|| hasParam("CantUntap") || hasParam("CantUntapPlayer")) {
if (hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")) {
layers.add(StaticAbilityLayer.RULES);
}

View File

@@ -124,7 +124,7 @@ public class StaticAbilityCantTarget {
}
}
return true;
return common(st, spellAbility);
}
protected static boolean common(final StaticAbility st, final SpellAbility spellAbility) {

View File

@@ -677,16 +677,6 @@ public final class StaticAbilityContinuous {
int v = AbilityUtils.calculateAmount(hostCard, params.get("CanBlockAmount"), stAb, true);
affectedCard.addCanBlockAdditional(v, se.getTimestamp());
}
if (params.containsKey("CantUntap")) {
affectedCard.addCantUntap(se.getTimestamp());
}
if (params.containsKey("CantUntapPlayer")) {
Player p = Iterables.getFirst(
AbilityUtils.getDefinedPlayers(hostCard, params.get("CantUntapPlayer"), null), null);
if (p != null) {
affectedCard.addCantUntapPlayer(p, se.getTimestamp());
}
}
}
if (mayLookAt != null) {

View File

@@ -113,9 +113,6 @@ public enum TrackableProperty {
OpponentMayLook(TrackableTypes.BooleanType),
BlockAdditional(TrackableTypes.IntegerType),
BlockAny(TrackableTypes.BooleanType),
CantUntapTurns(TrackableTypes.IntegerType),
CantUntapPlayer(TrackableTypes.PlayerViewCollectionType),
CantUntapAny(TrackableTypes.BooleanType),
AbilityText(TrackableTypes.StringType),
NonAbilityText(TrackableTypes.StringType),
FoilIndex(TrackableTypes.IntegerType),
@@ -125,6 +122,7 @@ public enum TrackableProperty {
LobbyPlayerName(TrackableTypes.StringType),
AvatarIndex(TrackableTypes.IntegerType),
AvatarCardImageKey(TrackableTypes.StringType),
SleeveIndex(TrackableTypes.IntegerType),
Opponents(TrackableTypes.PlayerViewCollectionType),
Life(TrackableTypes.IntegerType),
PoisonCounters(TrackableTypes.IntegerType),

View File

@@ -19,7 +19,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-gui-android</artifactId>
@@ -81,17 +81,17 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-android</version>
<version>28.1-android</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.7</version>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>xmlpull</groupId>

View File

@@ -27,6 +27,8 @@
-dontwarn java.lang.**
-dontwarn org.slf4j.**
-dontwarn javax.**
-dontwarn org.apache.logging.log4j.**
-dontwarn module-info
# mandatory proguard rules for cache2k to keep the core implementation
-dontwarn org.cache2k.impl.xmlConfiguration.**
@@ -48,6 +50,7 @@
-keep class com.google.guava.** { *; }
-keep class com.google.common.** { *; }
-keep class io.sentry.event.Event { *; }
-keep class io.netty.util.internal.logging.** { *; }
-keepclassmembers class com.badlogic.gdx.backends.android.AndroidInput* {
<init>(com.badlogic.gdx.Application, android.content.Context, java.lang.Object, com.badlogic.gdx.backends.android.AndroidApplicationConfiguration);

View File

@@ -14,6 +14,7 @@ import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
@@ -71,7 +72,8 @@ public class Main extends AndroidApplication {
Main.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir));
boolean value = Build.VERSION.SDK_INT >= 26;
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, value));
}
/*@Override

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-gui-desktop</artifactId>
@@ -142,12 +142,12 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-android</version>
<version>28.1-android</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -168,9 +168,14 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>com.googlecode</groupId>
@@ -180,7 +185,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>

View File

@@ -202,6 +202,14 @@ public class GuiDesktop implements IGuiBase {
return 0;
}
@Override
public int getSleevesCount() {
if (FSkin.isLoaded()) {
return FSkin.getSleeves().size();
}
return 0;
}
@Override
public String showFileDialog(final String title, final String defaultDir) {
final JFileChooser fc = new JFileChooser(defaultDir);

View File

@@ -383,6 +383,11 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
updateMatrix(FModel.getFormats().getStandard());
}
break;
case PIONEER_CARDGEN_DECK:
if(FModel.isdeckGenMatrixLoaded()) {
updateMatrix(FModel.getFormats().getPioneer());
}
break;
case MODERN_CARDGEN_DECK:
if(FModel.isdeckGenMatrixLoaded()) {
updateMatrix(FModel.getFormats().getModern());

View File

@@ -14,6 +14,7 @@ import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JPopupMenu;
import forge.screens.home.sanctioned.SleeveSelector;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.StringUtils;
@@ -66,7 +67,8 @@ public class PlayerPanel extends FPanel {
private final FLabel nameRandomiser;
private final FLabel avatarLabel = new FLabel.Builder().opaque(true).hoverable(true).iconScaleFactor(0.99f).iconInBackground(true).build();
private int avatarIndex;
private final FLabel sleeveLabel = new FLabel.Builder().opaque(true).hoverable(true).iconScaleFactor(0.99f).iconInBackground(true).build();
private int avatarIndex, sleeveIndex;
private final FTextField txtPlayerName = new FTextField.Builder().build();
private FRadioButton radioHuman;
@@ -127,6 +129,10 @@ public class PlayerPanel extends FPanel {
createAvatar();
this.add(avatarLabel, "spany 2, width 80px, height 80px");
/*TODO Layout and Override for PC*/
//createSleeve();
//this.add(sleeveLabel, "spany 2, width 60px, height 80px");
createNameEditor();
this.add(lobby.newLabel(localizer.getMessage("lblName") +":"), "w 40px, h 30px, gaptop 5px");
this.add(txtPlayerName, "h 30px, pushx, growx");
@@ -204,6 +210,10 @@ public class PlayerPanel extends FPanel {
avatarLabel.setIcon(FSkin.getAvatars().get(Integer.valueOf(type == LobbySlotType.OPEN ? -1 : avatarIndex)));
avatarLabel.repaintSelf();
sleeveLabel.setEnabled(mayEdit);
sleeveLabel.setIcon(FSkin.getSleeves().get(Integer.valueOf(type == LobbySlotType.OPEN ? -1 : sleeveIndex)));
sleeveLabel.repaintSelf();
txtPlayerName.setEnabled(mayEdit);
txtPlayerName.setText(type == LobbySlotType.OPEN ? StringUtils.EMPTY : playerName);
nameRandomiser.setEnabled(mayEdit);
@@ -332,6 +342,62 @@ public class PlayerPanel extends FPanel {
}
};
/** Listens to sleeve buttons and gives the appropriate player focus. */
private final FocusAdapter sleeveFocusListener = new FocusAdapter() {
@Override
public void focusGained(final FocusEvent e) {
lobby.changePlayerFocus(index);
}
};
private final FMouseAdapter sleeveMouseListener = new FMouseAdapter() {
@Override public final void onLeftClick(final MouseEvent e) {
if (!sleeveLabel.isEnabled()) {
return;
}
final FLabel sleeve = (FLabel)e.getSource();
lobby.changePlayerFocus(index);
sleeve.requestFocusInWindow();
final SleeveSelector sSel = new SleeveSelector(playerName, sleeveIndex, lobby.getUsedSleeves());
for (final FLabel lbl : sSel.getSelectables()) {
lbl.setCommand(new UiCommand() {
@Override
public void run() {
setSleeveIndex(Integer.valueOf(lbl.getName().substring(11)));
sSel.setVisible(false);
}
});
}
sSel.setVisible(true);
sSel.dispose();
if (index < 2) {
lobby.updateSleevePrefs();
}
lobby.firePlayerChangeListener(index);
}
@Override public final void onRightClick(final MouseEvent e) {
if (!sleeveLabel.isEnabled()) {
return;
}
lobby.changePlayerFocus(index);
sleeveLabel.requestFocusInWindow();
setRandomSleeve();
if (index < 2) {
lobby.updateSleevePrefs();
}
}
};
private void updateVariantControlsVisibility() {
final boolean isOathbreaker = lobby.hasVariant(GameType.Oathbreaker);
final boolean isTinyLeaders = lobby.hasVariant(GameType.TinyLeaders);
@@ -703,6 +769,20 @@ public class PlayerPanel extends FPanel {
avatarLabel.addMouseListener(avatarMouseListener);
}
private void createSleeve() {
final String[] currentPrefs = FModel.getPreferences().getPref(FPref.UI_SLEEVES).split(",");
if (index < currentPrefs.length) {
sleeveIndex = Integer.parseInt(currentPrefs[index]);
sleeveLabel.setIcon(FSkin.getSleeves().get(sleeveIndex));
} else {
setRandomSleeve(false);
}
sleeveLabel.setToolTipText("L-click: Select sleeve. R-click: Randomize sleeve.");
sleeveLabel.addFocusListener(sleeveFocusListener);
sleeveLabel.addMouseListener(sleeveMouseListener);
}
/** Applies a random avatar, avoiding avatars already used. */
private void setRandomAvatar() {
setRandomAvatar(true);
@@ -721,6 +801,24 @@ public class PlayerPanel extends FPanel {
}
}
/** Applies a random sleeve, avoiding sleeve already used. */
private void setRandomSleeve() {
setRandomSleeve(true);
}
private void setRandomSleeve(final boolean fireListeners) {
int random = 0;
final List<Integer> usedSleeves = lobby.getUsedSleeves();
do {
random = MyRandom.getRandom().nextInt(FSkin.getSleeves().size());
} while (usedSleeves.contains(random));
setSleeveIndex(random);
if (fireListeners) {
lobby.firePlayerChangeListener(index);
}
}
private final FSkin.LineSkinBorder focusedBorder = new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_BORDERS).alphaColor(255), 3);
private final FSkin.LineSkinBorder defaultBorder = new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_THEME).alphaColor(200), 2);
@@ -746,6 +844,16 @@ public class PlayerPanel extends FPanel {
avatarLabel.repaintSelf();
}
public int getSleeveIndex() {
return sleeveIndex;
}
public void setSleeveIndex(final int sleeveIndex0) {
sleeveIndex = sleeveIndex0;
final SkinImage icon = FSkin.getSleeves().get(sleeveIndex);
sleeveLabel.setIcon(icon);
sleeveLabel.repaintSelf();
}
public int getTeam() {
return teamComboBox.getSelectedIndex();
}

View File

@@ -3,6 +3,7 @@ package forge.screens.home;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.GuiBase;
import forge.UiCommand;
import forge.ai.AIOption;
import forge.deck.*;
@@ -222,6 +223,7 @@ public class VLobby implements ILobbyView {
DeckType selectedDeckType = deckChooser.getSelectedDeckType();
switch (selectedDeckType){
case STANDARD_CARDGEN_DECK:
case PIONEER_CARDGEN_DECK:
case MODERN_CARDGEN_DECK:
case LEGACY_CARDGEN_DECK:
case VINTAGE_CARDGEN_DECK:
@@ -258,6 +260,9 @@ public class VLobby implements ILobbyView {
addPlayerBtn.setEnabled(activePlayersNum < MAX_PLAYERS);
final boolean allowNetworking = lobby.isAllowNetworking();
GuiBase.setNetworkplay(allowNetworking);
ImmutableList<VariantCheckBox> vntBoxes = null;
if (allowNetworking) {
vntBoxes = vntBoxesNetwork;
@@ -397,7 +402,7 @@ public class VLobby implements ILobbyView {
private UpdateLobbyPlayerEvent getSlot(final int index) {
final PlayerPanel panel = playerPanels.get(index);
return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions());
return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), -1/*TODO panel.getSleeveIndex()*/, panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions());
}
/** Builds the actual deck panel layouts for each player.
@@ -858,6 +863,15 @@ public class VLobby implements ILobbyView {
prefs.save();
}
/** Saves sleeve prefs for players one and two. */
void updateSleevePrefs() {
final int pOneIndex = playerPanels.get(0).getSleeveIndex();
final int pTwoIndex = playerPanels.get(1).getSleeveIndex();
prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex);
prefs.save();
}
/** Adds a pre-styled FLabel component with the specified title. */
FLabel newLabel(final String title) {
return new FLabel.Builder().text(title).fontSize(14).fontStyle(Font.ITALIC).build();
@@ -871,6 +885,14 @@ public class VLobby implements ILobbyView {
return usedAvatars;
}
List<Integer> getUsedSleeves() {
final List<Integer> usedSleeves = Lists.newArrayListWithCapacity(MAX_PLAYERS);
for (final PlayerPanel pp : playerPanels) {
usedSleeves.add(pp.getSleeveIndex());
}
return usedSleeves;
}
private static final ImmutableList<String> genderOptions = ImmutableList.of("Male", "Female", "Any"),
typeOptions = ImmutableList.of("Fantasy", "Generic", "Any");

View File

@@ -72,6 +72,7 @@ public enum CSubmenuGauntletQuick implements ICDoc {
if (view.getBoxColorDecks().isSelected()) { allowedDeckTypes.add(DeckType.COLOR_DECK); }
if (view.getBoxStandardColorDecks().isSelected()) { allowedDeckTypes.add(DeckType.STANDARD_COLOR_DECK); }
if (view.getBoxStandardGenDecks().isSelected()) { allowedDeckTypes.add(DeckType.STANDARD_CARDGEN_DECK); }
if (view.getBoxPioneerGenDecks().isSelected()) { allowedDeckTypes.add(DeckType.PIONEER_CARDGEN_DECK); }
if (view.getBoxModernGenDecks().isSelected()) { allowedDeckTypes.add(DeckType.MODERN_CARDGEN_DECK); }
if (view.getBoxLegacyGenDecks().isSelected()) { allowedDeckTypes.add(DeckType.LEGACY_CARDGEN_DECK); }
if (view.getBoxVintageGenDecks().isSelected()) { allowedDeckTypes.add(DeckType.VINTAGE_CARDGEN_DECK); }

View File

@@ -56,6 +56,7 @@ public enum VSubmenuGauntletQuick implements IVSubmenu<CSubmenuGauntletQuick> {
private final JCheckBox boxColorDecks = new FCheckBox(DeckType.COLOR_DECK.toString());
private final JCheckBox boxStandardColorDecks = new FCheckBox(DeckType.STANDARD_COLOR_DECK.toString());
private final JCheckBox boxStandardCardgenDecks = new FCheckBox(DeckType.STANDARD_CARDGEN_DECK.toString());
private final JCheckBox boxPioneerCardgenDecks = new FCheckBox(DeckType.PIONEER_CARDGEN_DECK.toString());
private final JCheckBox boxModernCardgenDecks = new FCheckBox(DeckType.MODERN_CARDGEN_DECK.toString());
private final JCheckBox boxLegacyCardgenDecks = new FCheckBox(DeckType.LEGACY_CARDGEN_DECK.toString());
private final JCheckBox boxVintageCardgenDecks = new FCheckBox(DeckType.VINTAGE_CARDGEN_DECK.toString());
@@ -88,11 +89,13 @@ public enum VSubmenuGauntletQuick implements IVSubmenu<CSubmenuGauntletQuick> {
boxStandardColorDecks.setSelected(true);
if(FModel.isdeckGenMatrixLoaded()) {
boxStandardCardgenDecks.setSelected(true);
boxPioneerCardgenDecks.setSelected(true);
boxModernCardgenDecks.setSelected(true);
boxLegacyCardgenDecks.setSelected(true);
boxVintageCardgenDecks.setSelected(true);
}else{
boxStandardCardgenDecks.setSelected(false);
boxPioneerCardgenDecks.setSelected(false);
boxModernCardgenDecks.setSelected(false);
boxLegacyCardgenDecks.setSelected(false);
boxVintageCardgenDecks.setSelected(false);
@@ -121,6 +124,7 @@ public enum VSubmenuGauntletQuick implements IVSubmenu<CSubmenuGauntletQuick> {
pnlOptions.add(boxColorDecks, "w 96%!, h 30px!, gap 2% 0 0 5px");
if(FModel.isdeckGenMatrixLoaded()) {
pnlOptions.add(boxStandardCardgenDecks, "w 96%!, h 30px!, gap 2% 0 0 5px");
pnlOptions.add(boxPioneerCardgenDecks, "w 96%!, h 30px!, gap 2% 0 0 5px");
pnlOptions.add(boxModernCardgenDecks, "w 96%!, h 30px!, gap 2% 0 0 5px");
pnlOptions.add(boxLegacyCardgenDecks, "w 96%!, h 30px!, gap 2% 0 0 5px");
pnlOptions.add(boxVintageCardgenDecks, "w 96%!, h 30px!, gap 2% 0 0 5px");
@@ -221,6 +225,9 @@ public enum VSubmenuGauntletQuick implements IVSubmenu<CSubmenuGauntletQuick> {
public JCheckBox getBoxModernGenDecks() {
return boxModernCardgenDecks;
}
public JCheckBox getBoxPioneerGenDecks() {
return boxPioneerCardgenDecks;
}
public JCheckBox getBoxLegacyGenDecks() {
return boxLegacyCardgenDecks;
}

View File

@@ -0,0 +1,71 @@
package forge.screens.home.sanctioned;
import forge.gui.WrapLayout;
import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.view.FDialog;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@SuppressWarnings("serial")
public class SleeveSelector extends FDialog {
private final List<FLabel> selectables = new ArrayList<>();
private final Map<Integer, FSkin.SkinImage> sleeveMap = FSkin.getSleeves();
public SleeveSelector(final String playerName, final int currentIndex, final Collection<Integer> usedIndices) {
this.setTitle("Select Sleeve for " + playerName);
final JPanel pnlSleevePics = new JPanel(new WrapLayout());
pnlSleevePics.setOpaque(false);
pnlSleevePics.setOpaque(false);
final FLabel initialSelection = makeSleeveLabel(sleeveMap.get(currentIndex), currentIndex, currentIndex);
pnlSleevePics.add(initialSelection);
for (final Integer i : sleeveMap.keySet()) {
if (currentIndex != i) {
pnlSleevePics.add(makeSleeveLabel(sleeveMap.get(i), i, currentIndex));
}
}
final int width = this.getOwner().getWidth() * 3 / 4;
final int height = this.getOwner().getHeight() * 3 / 4;
this.setPreferredSize(new Dimension(width, height));
this.setSize(width, height);
final FScrollPane scroller = new FScrollPane(pnlSleevePics, false);
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
this.add(scroller, "w 100%-24px!, pushy, growy, gap 12px 0 0 0");
this.setDefaultFocus(initialSelection);
}
private FLabel makeSleeveLabel(final FSkin.SkinImage img0, final int index0, final int oldIndex) {
final FLabel lbl = new FLabel.Builder().icon(img0).iconScaleFactor(0.95).iconAlignX(SwingConstants.CENTER)
.iconInBackground(true).hoverable(true).selectable(true).selected(oldIndex == index0)
.unhoveredAlpha(oldIndex == index0 ? 0.9f : 0.7f).build();
final Dimension size = new Dimension(60, 80);
lbl.setPreferredSize(size);
lbl.setMaximumSize(size);
lbl.setMinimumSize(size);
lbl.setName("SleeveLabel" + index0);
if (oldIndex == index0) {
lbl.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_BORDERS).alphaColor(255), 3));
}
selectables.add(lbl);
return lbl;
}
public List<FLabel> getSelectables() {
return this.selectables;
}
}

View File

@@ -859,6 +859,7 @@ public class FSkin {
}
private static Map<Integer, SkinImage> avatars;
private static Map<Integer, SkinImage> sleeves;
private static Map<Integer, Font> fixedFonts = new HashMap<>();
public static Font getFixedFont() {
@@ -1039,7 +1040,7 @@ public class FSkin {
private static String preferredDir;
private static String preferredName;
private static BufferedImage bimDefaultSprite, bimFavIcon, bimPreferredSprite, bimFoils, bimQuestDraftDeck,
bimOldFoils, bimDefaultAvatars, bimPreferredAvatars, bimTrophies, bimAbilities, bimManaIcons;
bimOldFoils, bimDefaultAvatars, bimPreferredAvatars, bimTrophies, bimAbilities, bimManaIcons, bimDefaultSleeve, bimDefaultSleeve2;
private static int x0, y0, w0, h0, newW, newH, preferredW, preferredH;
private static int[] tempCoords;
private static int defaultFontSize = 12;
@@ -1173,6 +1174,8 @@ public class FSkin {
final File f9 = new File(defaultDir + ForgeConstants.SPRITE_FAVICONS_FILE);
final File f10 = new File(defaultDir + ForgeConstants.SPRITE_ABILITY_FILE);
final File f11 = new File(defaultDir + ForgeConstants.SPRITE_MANAICONS_FILE);
final File f12 = new File(defaultDir + ForgeConstants.SPRITE_SLEEVES_FILE);
final File f13 = new File(defaultDir + ForgeConstants.SPRITE_SLEEVES2_FILE);
try {
int p = 0;
@@ -1190,6 +1193,10 @@ public class FSkin {
FView.SINGLETON_INSTANCE.incrementSplashProgessBar(++p);
bimDefaultAvatars = ImageIO.read(f4);
FView.SINGLETON_INSTANCE.incrementSplashProgessBar(++p);
bimDefaultSleeve = ImageIO.read(f12);
FView.SINGLETON_INSTANCE.incrementSplashProgessBar(++p);
bimDefaultSleeve2 = ImageIO.read(f13);
FView.SINGLETON_INSTANCE.incrementSplashProgessBar(++p);
bimTrophies = ImageIO.read(f7);
FView.SINGLETON_INSTANCE.incrementSplashProgessBar(++p);
bimQuestDraftDeck = ImageIO.read(f8);
@@ -1255,6 +1262,8 @@ public class FSkin {
// Assemble avatar images
assembleAvatars();
// Sleeves
assembleSleeves();
// Images loaded; can start UI init.
FView.SINGLETON_INSTANCE.setSplashProgessBarMessage("Creating display components.");
@@ -1266,6 +1275,8 @@ public class FSkin {
bimOldFoils.flush();
bimPreferredSprite.flush();
bimDefaultAvatars.flush();
bimDefaultSleeve.flush();
bimDefaultSleeve2.flush();
bimQuestDraftDeck.flush();
bimTrophies.flush();
bimAbilities.flush();
@@ -1278,6 +1289,8 @@ public class FSkin {
bimOldFoils = null;
bimPreferredSprite = null;
bimDefaultAvatars = null;
bimDefaultSleeve = null;
bimDefaultSleeve2 = null;
bimPreferredAvatars = null;
bimQuestDraftDeck = null;
bimTrophies = null;
@@ -1379,6 +1392,10 @@ public class FSkin {
return avatars;
}
public static Map<Integer, SkinImage> getSleeves() {
return sleeves;
}
public static boolean isLoaded() { return loaded; }
/**
@@ -1482,6 +1499,34 @@ public class FSkin {
}
}
private static void assembleSleeves() {
sleeves = new HashMap<>();
int counter = 0;
Color pxTest;
final int pw = bimDefaultSleeve.getWidth();
final int ph = bimDefaultSleeve.getHeight();
for (int j = 0; j < ph; j += 500) {
for (int i = 0; i < pw; i += 360) {
pxTest = getColorFromPixel(bimDefaultSleeve.getRGB(i + 180, j + 250));
if (pxTest.getAlpha() == 0) { continue; }
sleeves.put(counter++, new SkinImage(bimDefaultSleeve.getSubimage(i, j, 360, 500)));
}
}
//2nd set
final int aw = bimDefaultSleeve2.getWidth();
final int ah = bimDefaultSleeve2.getHeight();
for (int j = 0; j < ah; j += 500) {
for (int i = 0; i < aw; i += 360) {
pxTest = getColorFromPixel(bimDefaultSleeve2.getRGB(i + 180, j + 250));
if (pxTest.getAlpha() == 0) { continue; }
sleeves.put(counter++, new SkinImage(bimDefaultSleeve2.getSubimage(i, j, 360, 500)));
}
}
}
private static void setImage(final FSkinProp s0, final BufferedImage bim) {
tempCoords = s0.getCoords();
x0 = tempCoords[0];

View File

@@ -49,6 +49,9 @@ public final class Main {
//setup GUI interface
GuiBase.setInterface(new GuiDesktop());
//set PropertyConfig log4j to true
GuiBase.enablePropertyConfig(true);
//install our error handler
ExceptionHandler.registerErrorHandling();

View File

@@ -21,7 +21,6 @@ import forge.CachedCardImage;
import forge.FThreads;
import forge.StaticData;
import forge.card.CardEdition;
import forge.card.CardTranslation;
import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardView;
@@ -39,6 +38,7 @@ import forge.screens.match.CMatchUI;
import forge.toolbox.CardFaceSymbols;
import forge.toolbox.FSkin.SkinnedPanel;
import forge.toolbox.IDisposable;
import forge.util.CardTranslation;
import forge.view.arcane.util.OutlinedLabel;
import javax.swing.*;

View File

@@ -12,7 +12,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-gui-ios</artifactId>

View File

@@ -29,7 +29,7 @@ public class Main extends IOSApplication.Delegate {
final IOSApplicationConfiguration config = new IOSApplicationConfiguration();
config.useAccelerometer = false;
config.useCompass = false;
final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir);
final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir, false);
final IOSApplication iosApp = new IOSApplication(app, config);
return iosApp;
}

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-gui-mobile-dev</artifactId>

View File

@@ -93,7 +93,7 @@ public class Main {
config.useHDPI = desktopMode; // enable HiDPI on Mac OS
new LwjglApplication(Forge.getApp(new LwjglClipboard(), new DesktopAdapter(switchOrientationFile),
desktopMode ? desktopModeAssetsDir : assetsDir), config);
desktopMode ? desktopModeAssetsDir : assetsDir, true), config);
}
private static class DesktopAdapter implements IDeviceAdapter {

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-gui-mobile</artifactId>
@@ -48,17 +48,17 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-android</version>
<version>28.1-android</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.7</version>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>

View File

@@ -10,7 +10,6 @@ import forge.assets.AssetsDownloader;
import forge.assets.FSkin;
import forge.assets.FSkinFont;
import forge.assets.ImageCache;
import forge.card.CardTranslation;
import forge.error.BugReporter;
import forge.error.ExceptionHandler;
import forge.interfaces.IDeviceAdapter;
@@ -31,6 +30,7 @@ import forge.toolbox.FGestureAdapter;
import forge.toolbox.FOptionPane;
import forge.toolbox.FOverlay;
import forge.util.Callback;
import forge.util.CardTranslation;
import forge.util.FileUtil;
import forge.util.Localizer;
import forge.util.Utils;
@@ -67,11 +67,12 @@ public class Forge implements ApplicationListener {
public static boolean enablePreloadExtendedArt = false;
public static String locale = "en-US";
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0) {
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value) {
if (GuiBase.getInterface() == null) {
clipboard = clipboard0;
deviceAdapter = deviceAdapter0;
GuiBase.setInterface(new GuiMobile(assetDir0));
GuiBase.enablePropertyConfig(value);
}
return app;
}
@@ -129,13 +130,13 @@ public class Forge implements ApplicationListener {
FSkinFont.preloadAll(locale);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblLoadingCardTranslations"));
CardTranslation.preloadTranslation(locale);
CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR);
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblFinishingStartup"));
//add reminder to preload
if (enablePreloadExtendedArt)
splashScreen.getProgressBar().setDescription("Preload Extended Art...");
splashScreen.getProgressBar().setDescription(localizer.getMessage("lblPreloadExtendedArt"));
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {

View File

@@ -244,6 +244,14 @@ public class GuiMobile implements IGuiBase {
return 0;
}
@Override
public int getSleevesCount() {
if (FSkin.isLoaded()) {
return FSkin.getSleeves().size();
}
return 0;
}
@Override
public String showFileDialog(final String title, final String defaultDir) {
return ForgeConstants.USER_GAMES_DIR + "Test.fgs"; //TODO: Show dialog

View File

@@ -16,7 +16,6 @@
package forge.assets;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.PixmapIO;
@@ -25,29 +24,30 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
import com.badlogic.gdx.graphics.g2d.PixmapPacker.Page;
import com.badlogic.gdx.utils.Array;
/** A utility to output BitmapFontData to a FNT file. This can be useful for caching the result from TrueTypeFont, for faster load
* times.
*
* The font format is from the AngelCodeFont BMFont tool.
*
* @author mattdesl AKA davedes */
/**
* This file is 'borrowed' from gdx-tools in the libgdx source
*/
public class BitmapFontWriter {
/** A utility to output BitmapFontData to a FNT file. This can be useful for caching the result from TrueTypeFont, for faster load
* times.
* <p>
* The font file format is from the AngelCodeFont BMFont tool.
* <p>
* Output is nearly identical to the FreeType settting in the Hiero tool {@Link com.badlogic.gdx.tools.hiero.Hiero}. BitmapFontWriter gives more flexibility, eg
* borders and shadows can be used. Hiero is able to avoid outputting the same glyph image more than once if multiple character
* codes have the exact same glyph.
* @author mattdesl AKA davedes */
public class BitmapFontWriter {
/** The output format. */
public enum OutputFormat {
public static enum OutputFormat {
/** AngelCodeFont text format */
Text,
/** AngelCodeFont XML format */
XML
XML;
}
/** The output format */
private static OutputFormat format = OutputFormat.Text;
@@ -55,26 +55,25 @@ import com.badlogic.gdx.utils.Array;
* Pixi.js).
*
* @param fmt the output format to use */
public static void setOutputFormat(OutputFormat fmt) {
if (fmt==null)
throw new NullPointerException("format cannot be null");
public static void setOutputFormat (OutputFormat fmt) {
if (fmt == null) throw new NullPointerException("format cannot be null");
format = fmt;
}
/** Returns the currently used output format.
* @return the output format */
public static OutputFormat getOutputFormat() {
public static OutputFormat getOutputFormat () {
return format;
}
/** The Padding parameter for FontInfo. */
public static class Padding {
public int up, down, left, right;
public Padding() {
public Padding () {
}
public Padding(int up, int down, int left, int right) {
public Padding (int up, int down, int left, int right) {
this.up = up;
this.down = down;
this.left = left;
@@ -87,8 +86,8 @@ import com.badlogic.gdx.utils.Array;
public int horizontal, vertical;
}
/** The font "info" line; this will be ignored by LibGDX's BitmapFont reader,
* but useful for clean and organized output. */
/** The font "info" line; everything except padding and override metrics are ignored by LibGDX's BitmapFont reader, it is otherwise just useful for
* clean and organized output. */
public static class FontInfo {
/** Face name */
public String face;
@@ -113,32 +112,55 @@ import com.badlogic.gdx.utils.Array;
/** Horizontal/vertical spacing that was applied to font */
public Spacing spacing = new Spacing();
public int outline = 0;
public FontInfo() {
/** Override metrics */
public boolean hasOverrideMetrics;
public float ascent;
public float descent;
public float down;
public float capHeight;
public float lineHeight;
public float spaceXAdvance;
public float xHeight;
public FontInfo () {
}
public FontInfo(String face, int size) {
public FontInfo (String face, int size) {
this.face = face;
this.size = size;
}
public void overrideMetrics (BitmapFontData data) {
hasOverrideMetrics = true;
ascent = data.ascent;
descent = data.descent;
down = data.down;
capHeight = data.capHeight;
lineHeight = data.lineHeight;
spaceXAdvance = data.spaceXadvance;
xHeight = data.xHeight;
}
}
private static String quote(Object params) {
private static String quote (Object params) {
return quote(params, false);
}
private static String quote(Object params, boolean spaceAfter) {
private static String quote (Object params, boolean spaceAfter) {
if (BitmapFontWriter.getOutputFormat() == OutputFormat.XML)
return "\"" + params.toString().trim() + "\"" + (spaceAfter ? " " : "");
else
return params.toString();
}
/** Writes the given BitmapFontData to a file, using the specified <tt>pageRefs</tt> strings as the image paths for each texture
* page. The glyphs in BitmapFontData have a "page" id, which references the index of the pageRef you specify here.
/** Writes the given BitmapFontData to a file, using the specified <tt>pageRefs</tt> strings as the image paths for each
* texture page. The glyphs in BitmapFontData have a "page" id, which references the index of the pageRef you specify here.
*
* The FontInfo parameter is useful for cleaner output; such as including a size and font face name hint. However, it can be
* null to use default values. Ultimately, LibGDX ignores the "info" line when reading back fonts.
* null to use default values. LibGDX ignores most of the "info" line when reading back fonts, only padding is used. Padding
* also affects the size, location, and offset of the glyphs that are output.
*
* Likewise, the scaleW and scaleH are only for cleaner output. They are currently ignored by LibGDX's reader. For maximum
* compatibility with other BMFont tools, you should use the width and height of your texture pages (each page should be the
@@ -150,21 +172,22 @@ import com.badlogic.gdx.utils.Array;
* @param info the optional info for the file header; can be null
* @param scaleW the width of your texture pages
* @param scaleH the height of your texture pages */
public static void writeFont (BitmapFontData fontData, String[] pageRefs, FileHandle outFntFile, FontInfo info, int scaleW, int scaleH) {
if (info==null) {
public static void writeFont (BitmapFontData fontData, String[] pageRefs, FileHandle outFntFile, FontInfo info, int scaleW,
int scaleH) {
if (info == null) {
info = new FontInfo();
info.face = outFntFile.nameWithoutExtension();
}
int lineHeight = (int)fontData.lineHeight;
int pages = pageRefs.length;
int packed = 0;
int base = (int)((fontData.capHeight) + (fontData.flipped ? -fontData.ascent : fontData.ascent));
OutputFormat fmt = BitmapFontWriter.getOutputFormat();
boolean xml = fmt == OutputFormat.XML;
boolean xml = fmt == OutputFormat.XML;
StringBuilder buf = new StringBuilder();
if (xml) {
buf.append("<font>\n");
}
@@ -172,152 +195,129 @@ import com.badlogic.gdx.utils.Array;
String xmlCloseSelf = xml ? "/>" : "";
String xmlTab = xml ? "\t" : "";
String xmlClose = xml ? ">" : "";
String xmlQuote = xml ? "\"" : "";
String alphaChnlParams =
xml ? " alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\""
: " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0";
//INFO LINE
buf.append(xmlOpen)
.append("info face=\"")
.append(info.face==null ? "" : info.face.replaceAll("\"", "'"))
.append("\" size=").append( quote(info.size) )
.append(" bold=").append( quote(info.bold ? 1 : 0) )
.append(" italic=").append( quote(info.italic ? 1 : 0) )
.append(" charset=\"").append(info.charset==null ? "" : info.charset)
.append("\" unicode=").append( quote(info.unicode ? 1 : 0) )
.append(" stretchH=").append( quote(info.stretchH) )
.append(" smooth=").append( quote(info.smooth ? 1 : 0) )
.append(" aa=").append( quote(info.aa) )
.append(" padding=")
.append(xmlQuote)
.append(info.padding.up).append(",")
.append(info.padding.down).append(",")
.append(info.padding.left).append(",")
.append(info.padding.right)
.append(xmlQuote)
.append(" spacing=")
.append(xmlQuote)
.append(info.spacing.horizontal).append(",")
.append(info.spacing.vertical)
.append(xmlQuote)
.append(xmlCloseSelf)
String alphaChnlParams = xml ? " alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\""
: " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0";
// INFO LINE
buf.append(xmlOpen).append("info face=\"").append(info.face == null ? "" : info.face.replaceAll("\"", "'"))
.append("\" size=").append(quote(info.size)).append(" bold=").append(quote(info.bold ? 1 : 0)).append(" italic=")
.append(quote(info.italic ? 1 : 0)).append(" charset=\"").append(info.charset == null ? "" : info.charset)
.append("\" unicode=").append(quote(info.unicode ? 1 : 0)).append(" stretchH=").append(quote(info.stretchH))
.append(" smooth=").append(quote(info.smooth ? 1 : 0)).append(" aa=").append(quote(info.aa)).append(" padding=")
.append(xmlQuote).append(info.padding.up).append(",").append(info.padding.right).append(",").append(info.padding.down)
.append(",").append(info.padding.left).append(xmlQuote).append(" spacing=").append(xmlQuote)
.append(info.spacing.horizontal).append(",").append(info.spacing.vertical).append(xmlQuote).append(xmlCloseSelf)
.append("\n");
//COMMON line
buf.append(xmlOpen)
.append("common lineHeight=").append( quote(lineHeight) )
.append(" base=").append( quote(base) )
.append(" scaleW=").append( quote(scaleW) )
.append(" scaleH=").append( quote(scaleH) )
.append(" pages=").append( quote(pages) )
.append(" packed=").append( quote(packed) )
.append(alphaChnlParams)
.append(xmlCloseSelf)
.append("\n");
if (xml)
buf.append("\t<pages>\n");
//PAGES
for (int i=0; i<pageRefs.length; i++) {
buf.append(xmlTab)
.append(xmlOpen)
.append("page id=")
.append( quote(i) )
.append(" file=\"")
.append(pageRefs[i])
.append("\"")
.append(xmlCloseSelf)
.append("\n");
// COMMON line
buf.append(xmlOpen).append("common lineHeight=").append(quote(lineHeight)).append(" base=").append(quote(base))
.append(" scaleW=").append(quote(scaleW)).append(" scaleH=").append(quote(scaleH)).append(" pages=").append(quote(pages))
.append(" packed=").append(quote(packed)).append(alphaChnlParams).append(xmlCloseSelf).append("\n");
if (xml) buf.append("\t<pages>\n");
// PAGES
for (int i = 0; i < pageRefs.length; i++) {
buf.append(xmlTab).append(xmlOpen).append("page id=").append(quote(i)).append(" file=\"").append(pageRefs[i])
.append("\"").append(xmlCloseSelf).append("\n");
}
if (xml)
buf.append("\t</pages>\n");
//CHARS
Array<Glyph> glyphs = new Array<>(256);
for (int i=0; i<fontData.glyphs.length; i++) {
if (fontData.glyphs[i]==null)
continue;
for (int j=0; j<fontData.glyphs[i].length; j++) {
if (fontData.glyphs[i][j]!=null) {
if (xml) buf.append("\t</pages>\n");
// CHARS
Array<Glyph> glyphs = new Array<Glyph>(256);
for (int i = 0; i < fontData.glyphs.length; i++) {
if (fontData.glyphs[i] == null) continue;
for (int j = 0; j < fontData.glyphs[i].length; j++) {
if (fontData.glyphs[i][j] != null) {
glyphs.add(fontData.glyphs[i][j]);
}
}
}
buf.append(xmlOpen)
.append("chars count=").append(quote(glyphs.size))
.append(xmlClose)
.append("\n");
//CHAR definitions
for (int i=0; i<glyphs.size; i++) {
buf.append(xmlOpen).append("chars count=").append(quote(glyphs.size)).append(xmlClose).append("\n");
int padLeft = 0, padRight = 0, padTop = 0, padX = 0, padY = 0;
if (info != null) {
padTop = info.padding.up;
padLeft = info.padding.left;
padRight = info.padding.right;
padX = padLeft + padRight;
padY = info.padding.up + info.padding.down;
}
// CHAR definitions
for (int i = 0; i < glyphs.size; i++) {
Glyph g = glyphs.get(i);
buf.append(xmlTab)
.append(xmlOpen)
.append("char id=")
.append(quote( String.format("%-5s", g.id), true ))
.append("x=").append(quote( String.format("%-5s", g.srcX), true ) )
.append("y=").append(quote( String.format("%-5s", g.srcY), true ) )
.append("width=").append(quote( String.format("%-5s", g.width), true ) )
.append("height=").append(quote( String.format("%-5s", g.height), true ) )
.append("xoffset=").append(quote( String.format("%-5s", g.xoffset), true ) )
.append("yoffset=").append(quote( String.format("%-5s", fontData.flipped ? g.yoffset : -(g.height + g.yoffset) ), true ) )
.append("xadvance=").append(quote( String.format("%-5s", g.xadvance), true ) )
.append("page=").append(quote( String.format("%-5s", g.page), true ) )
.append("chnl=").append(quote(0, true))
.append(xmlCloseSelf)
boolean empty = g.width == 0 || g.height == 0;
buf.append(xmlTab).append(xmlOpen).append("char id=").append(quote(String.format("%-6s", g.id), true)).append("x=")
.append(quote(String.format("%-5s", empty ? 0 : g.srcX), true)).append("y=")
.append(quote(String.format("%-5s", empty ? 0 : g.srcY), true)).append("width=")
.append(quote(String.format("%-5s", empty ? 0 : g.width), true)).append("height=")
.append(quote(String.format("%-5s", empty ? 0 : g.height), true)).append("xoffset=")
.append(quote(String.format("%-5s", g.xoffset - padLeft), true)).append("yoffset=")
.append(quote(String.format("%-5s", fontData.flipped ? g.yoffset + padTop : -(g.height + (g.yoffset + padTop))), true))
.append("xadvance=").append(quote(String.format("%-5s", g.xadvance), true)).append("page=")
.append(quote(String.format("%-5s", g.page), true)).append("chnl=").append(quote(0, true)).append(xmlCloseSelf)
.append("\n");
}
if (xml)
buf.append("\t</chars>\n");
//KERNINGS
if (xml) buf.append("\t</chars>\n");
// KERNINGS
int kernCount = 0;
StringBuilder kernBuf = new StringBuilder();
StringBuilder kernBuf = new StringBuilder();
for (int i = 0; i < glyphs.size; i++) {
for (int j = 0; j < glyphs.size; j++) {
Glyph first = glyphs.get(i);
Glyph second = glyphs.get(j);
int kern = first.getKerning((char)second.id);
if (kern!=0) {
if (kern != 0) {
kernCount++;
kernBuf.append(xmlTab)
.append(xmlOpen)
.append("kerning first=").append(quote(first.id))
.append(" second=").append(quote(second.id))
.append(" amount=").append(quote(kern, true))
.append(xmlCloseSelf)
.append("\n");
kernBuf.append(xmlTab).append(xmlOpen).append("kerning first=").append(quote(first.id)).append(" second=")
.append(quote(second.id)).append(" amount=").append(quote(kern, true)).append(xmlCloseSelf).append("\n");
}
}
}
//KERN info
buf.append(xmlOpen)
.append("kernings count=").append(quote(kernCount))
.append(xmlClose)
.append("\n");
// KERN info
buf.append(xmlOpen).append("kernings count=").append(quote(kernCount)).append(xmlClose).append("\n");
buf.append(kernBuf);
if (xml) {
buf.append("\t</kernings>\n");
}
// Override metrics
if (info.hasOverrideMetrics) {
if (xml) buf.append("\t<metrics>\n");
buf.append(xmlTab).append(xmlOpen)
.append("metrics ascent=").append(quote(info.ascent, true))
.append(" descent=").append(quote(info.descent, true))
.append(" down=").append(quote(info.down, true))
.append(" capHeight=").append(quote(info.capHeight, true))
.append(" lineHeight=").append(quote(info.lineHeight, true))
.append(" spaceXAdvance=").append(quote(info.spaceXAdvance, true))
.append(" xHeight=").append(quote(info.xHeight, true))
.append(xmlCloseSelf).append("\n");
if (xml) buf.append("\t</metrics>\n");
}
if (xml) {
buf.append("</font>");
}
String charset = info.charset;
if (charset!=null&&charset.length()==0)
charset = null;
if (charset != null && charset.length() == 0) charset = null;
outFntFile.writeString(buf.toString(), false, charset);
}
/** A utility method which writes the given font data to a file.
*
* The specified pixmaps are written to the parent directory of <tt>outFntFile</tt>, using that file's name without an
@@ -337,8 +337,8 @@ import com.badlogic.gdx.utils.Array;
* @param info the optional font info for the header file, can be null */
public static void writeFont (BitmapFontData fontData, Pixmap[] pages, FileHandle outFntFile, FontInfo info) {
String[] pageRefs = writePixmaps(pages, outFntFile.parent(), outFntFile.nameWithoutExtension());
//write the font data
// write the font data
writeFont(fontData, pageRefs, outFntFile, info, pages[0].getWidth(), pages[0].getHeight());
}
@@ -357,18 +357,17 @@ import com.badlogic.gdx.utils.Array;
* @param fileName the file names for the output images
* @return the array of string references to be used with <tt>writeFont</tt> */
public static String[] writePixmaps (Pixmap[] pages, FileHandle outputDir, String fileName) {
if (pages==null || pages.length==0)
throw new IllegalArgumentException("no pixmaps supplied to BitmapFontWriter.write");
if (pages == null || pages.length == 0) throw new IllegalArgumentException("no pixmaps supplied to BitmapFontWriter.write");
String[] pageRefs = new String[pages.length];
for (int i=0; i<pages.length; i++) {
String ref = pages.length==1 ? (fileName+".png") : (fileName+"_"+i+".png");
//the ref for this image
for (int i = 0; i < pages.length; i++) {
String ref = pages.length == 1 ? (fileName + ".png") : (fileName + "_" + i + ".png");
// the ref for this image
pageRefs[i] = ref;
//write the PNG in that directory
// write the PNG in that directory
PixmapIO.writePNG(outputDir.child(ref), pages[i]);
}
return pageRefs;
@@ -383,9 +382,9 @@ import com.badlogic.gdx.utils.Array;
* @return the file refs */
public static String[] writePixmaps (Array<Page> pages, FileHandle outputDir, String fileName) {
Pixmap[] pix = new Pixmap[pages.size];
for (int i=0; i<pages.size; i++) {
for (int i = 0; i < pages.size; i++) {
pix[i] = pages.get(i).getPixmap();
}
return writePixmaps(pix, outputDir, fileName);
}
}
}

View File

@@ -1,11 +1,10 @@
package forge.assets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.badlogic.gdx.utils.Array;
import forge.Forge;
import org.apache.commons.lang3.text.WordUtils;
import com.badlogic.gdx.Gdx;
@@ -30,8 +29,10 @@ import forge.toolbox.FProgressBar;
public class FSkin {
private static final Map<FSkinProp, FSkinImage> images = new HashMap<>();
private static final Map<Integer, TextureRegion> avatars = new HashMap<>();
private static final Map<Integer, TextureRegion> sleeves = new HashMap<>();
private static final Map<Integer, TextureRegion> borders = new HashMap<>();
private static List<String> allSkins;
private static Array<String> allSkins;
private static FileHandle preferredDir;
private static String preferredName;
private static boolean loaded = false;
@@ -98,12 +99,12 @@ public class FSkin {
else {
if (splashScreen != null) {
if (allSkins == null) { //initialize
allSkins = new ArrayList<>();
final List<String> skinDirectoryNames = getSkinDirectoryNames();
allSkins = new Array<>();
final Array<String> skinDirectoryNames = getSkinDirectoryNames();
for (final String skinDirectoryName : skinDirectoryNames) {
allSkins.add(WordUtils.capitalize(skinDirectoryName.replace('_', ' ')));
}
Collections.sort(allSkins);
allSkins.sort();
}
}
@@ -172,6 +173,9 @@ public class FSkin {
}
avatars.clear();
sleeves.clear();
boolean textureFilter = Forge.isTextureFilteringEnabled();
final Map<String, Texture> textures = new HashMap<>();
@@ -183,6 +187,9 @@ public class FSkin {
final FileHandle f5 = getSkinFile(ForgeConstants.SPRITE_AVATARS_FILE);
final FileHandle f6 = getDefaultSkinFile(SourceFile.OLD_FOILS.getFilename());
final FileHandle f7 = getDefaultSkinFile(ForgeConstants.SPRITE_MANAICONS_FILE);
final FileHandle f8 = getDefaultSkinFile(ForgeConstants.SPRITE_SLEEVES_FILE);
final FileHandle f9 = getDefaultSkinFile(ForgeConstants.SPRITE_SLEEVES2_FILE);
final FileHandle f10 = getDefaultSkinFile(ForgeConstants.SPRITE_BORDER_FILE);
try {
textures.put(f1.path(), new Texture(f1));
@@ -218,16 +225,25 @@ public class FSkin {
//assemble avatar textures
int counter = 0;
int scount = 0;
Color pxTest;
Pixmap pxDefaultAvatars, pxPreferredAvatars;
Texture txDefaultAvatars, txPreferredAvatars;
Pixmap pxDefaultAvatars, pxPreferredAvatars, pxDefaultSleeves;
Texture txDefaultAvatars, txPreferredAvatars, txDefaultSleeves;
pxDefaultAvatars = new Pixmap(f4);
txDefaultAvatars = new Texture(f4);
pxDefaultSleeves = new Pixmap(f8);
txDefaultAvatars = new Texture(f4, textureFilter);
if (textureFilter)
txDefaultAvatars.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
txDefaultSleeves = new Texture(f8, textureFilter);
if (textureFilter)
txDefaultSleeves.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
if (f5.exists()) {
pxPreferredAvatars = new Pixmap(f5);
txPreferredAvatars = new Texture(f5);
txPreferredAvatars = new Texture(f5, textureFilter);
if (textureFilter)
txPreferredAvatars.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
final int pw = pxPreferredAvatars.getWidth();
final int ph = pxPreferredAvatars.getHeight();
@@ -255,8 +271,42 @@ public class FSkin {
}
}
final int sw = pxDefaultSleeves.getWidth();
final int sh = pxDefaultSleeves.getHeight();
for (int j = 0; j < sh; j += 500) {
for (int i = 0; i < sw; i += 360) {
pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250));
if (pxTest.a == 0) { continue; }
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
}
}
//re init second set of sleeves
pxDefaultSleeves = new Pixmap(f9);
txDefaultSleeves = new Texture(f9, textureFilter);
if (textureFilter)
txDefaultSleeves.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
final int sw2 = pxDefaultSleeves.getWidth();
final int sh2 = pxDefaultSleeves.getHeight();
for (int j = 0; j < sh2; j += 500) {
for (int i = 0; i < sw2; i += 360) {
pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250));
if (pxTest.a == 0) { continue; }
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
}
}
Texture bordersBW = new Texture(f10);
FSkin.borders.put(0, new TextureRegion(bordersBW, 2, 2, 672, 936));
FSkin.borders.put(1, new TextureRegion(bordersBW, 676, 2, 672, 936));
preferredIcons.dispose();
pxDefaultAvatars.dispose();
pxDefaultSleeves.dispose();;
}
catch (final Exception e) {
System.err.println("FSkin$loadFull: Missing a sprite (default icons, "
@@ -314,8 +364,8 @@ public class FSkin {
*
* @return the skins
*/
public static List<String> getSkinDirectoryNames() {
final List<String> mySkins = new ArrayList<>();
public static Array<String> getSkinDirectoryNames() {
final Array<String> mySkins = new Array<>();
final FileHandle dir = Gdx.files.absolute(ForgeConstants.SKINS_DIR);
for (FileHandle skinFile : dir.list()) {
@@ -340,5 +390,13 @@ public class FSkin {
return avatars;
}
public static Map<Integer, TextureRegion> getSleeves() {
return sleeves;
}
public static Map<Integer, TextureRegion> getBorders() {
return borders;
}
public static boolean isLoaded() { return loaded; }
}

View File

@@ -463,7 +463,7 @@ public class FSkinFont {
+ "驽驾驿骁骂骄骆骇验骏骐骑骗骚骤骨骰骷骸骼髅髓高鬃鬓鬣鬼魁魂魄"
+ "魅魇魈魏魔鰴鱼鲁鲜鲤鲨鲮鲸鲽鳃鳄鳍鳐鳗鳝鳞鸟鸠鸡鸢鸣鸦鸽鹅鹉"
+ "鹊鹏鹗鹞鹤鹦鹫鹭鹰鹿麋麒麟麦麻黄黎黏黑默黛黜點黠黯鼎鼓鼠鼬鼹"
+ "鼻齐齑齿龇龙龟!(),/:;?~";
+ "鼻齐齑齿龇龙龟";
final PixmapPacker packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 2, false);
final FreeTypeFontParameter parameter = new FreeTypeFontParameter();

View File

@@ -214,6 +214,16 @@ public class ImageCache {
return Color.valueOf("#fffffd");
return Color.valueOf("#171717");
}
public static int getFSkinBorders(CardView c) {
if (c == null)
return 0;
CardView.CardStateView state = c.getCurrentState();
CardEdition ed = FModel.getMagicDb().getEditions().get(state.getSetCode());
if (ed != null && ed.isWhiteBorder() && state.getFoilIndex() == 0)
return 1;
return 0;
}
public static boolean isExtendedArt(CardView c) {
if (c == null)
return false;

View File

@@ -21,9 +21,10 @@ final class ImageLoader extends CacheLoader<String, Texture> {
boolean extendedArt = false;
boolean textureFilter = Forge.isTextureFilteringEnabled();
if (key.length() > 4){
if ((key.substring(0,4).contains("MPS_"))) //TODO add sets to get all extended art???
if ((key.substring(0,4).contains("MPS_"))) //MPS_ sets
extendedArt = true;
else if ((key.substring(0,3).contains("UST"))) //Unstable Set
extendedArt = true;
//use generated extended art... it will preload the cache at startup so... yeah! :)
}
File file = ImageKeys.getImageFile(key);
if (file != null) {

View File

@@ -6,7 +6,10 @@ import com.badlogic.gdx.utils.Align;
import com.google.common.collect.ImmutableList;
import forge.Forge;
import forge.Graphics;
import forge.ImageKeys;
import forge.assets.FBufferedImage;
import forge.assets.FImage;
import forge.assets.FSkin;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
@@ -25,6 +28,7 @@ import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.screens.FScreen;
import forge.screens.match.MatchController;
import forge.util.CardTranslation;
import forge.util.Utils;
import org.apache.commons.lang3.StringUtils;
@@ -326,7 +330,9 @@ public class CardImageRenderer {
}
public static void drawZoom(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h, float dispW, float dispH, boolean isCurrentCard) {
boolean canshow = MatchController.instance.mayView(card);
final Texture image = ImageCache.getImage(card.getState(altState).getImageKey(MatchController.instance.getLocalPlayers()), true);
FImage sleeves = MatchController.getPlayerSleeve(card.getOwner());
if (image == null) { //draw details if can't draw zoom
drawDetails(g, card, gameView, altState, x, y, w, h);
return;
@@ -337,8 +343,6 @@ public class CardImageRenderer {
return;
}
boolean canLook = MatchController.instance.mayView(card);
if (image == ImageCache.defaultImage) { //support drawing card image manually if card image not found
drawCardImage(g, card, altState, x, y, w, h, CardStackPosition.Top);
}
@@ -358,47 +362,43 @@ public class CardImageRenderer {
if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
else {
if (rotatePlane)
g.drawfillBorder(3, ImageCache.borderColor(card), new_xRotate, new_yRotate, new_h, new_w, radius);
else
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x+radius/2.3f, new_y+radius/2, new_w*0.96f, new_h*0.96f, (new_x+radius/2.3f) + (new_w*0.96f) / 2, (new_y+radius/2) + (new_h*0.96f) / 2, -90);
g.drawRotatedImage(FSkin.getBorders().get(0), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x+radius/2, new_y+radius/2, new_w*0.96f, new_h*0.96f, (new_x+radius/2) + (new_w*0.96f) / 2, (new_y+radius/2) + (new_h*0.96f) / 2, -90);
}
}
else
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, -90);
} else if (rotateSplit && isCurrentCard && card.isSplitCard() && canLook) {
} else if (rotateSplit && isCurrentCard && card.isSplitCard() && canshow) {
boolean isAftermath = card.getText().contains("Aftermath") || card.getAlternateState().getOracleText().contains("Aftermath");
if (Forge.enableUIMask) {
if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
else {
if (rotateSplit)
g.drawfillBorder(3, ImageCache.borderColor(card), new_xRotate, new_yRotate, new_h, new_w, radius);
else
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x + radius / 2.3f, new_y + radius / 2, new_w * 0.96f, new_h * 0.96f, (new_x + radius / 2.3f) + (new_w * 0.96f) / 2, (new_y + radius / 2) + (new_h * 0.96f) / 2, isAftermath ? 90 : -90);
g.drawRotatedImage(FSkin.getBorders().get(ImageCache.getFSkinBorders(card)), new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), new_x + radius / 2, new_y + radius / 2, new_w * 0.96f, new_h * 0.96f, (new_x + radius / 2) + (new_w * 0.96f) / 2, (new_y + radius / 2) + (new_h * 0.96f) / 2, isAftermath ? 90 : -90);
}
}
else
g.drawRotatedImage(image, new_x, new_y, new_w, new_h, new_x + new_w / 2, new_y + new_h / 2, isAftermath ? 90 : -90);
}
else {
if (Forge.enableUIMask) {
if (Forge.enableUIMask && canshow && !ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE).equals(card.getState(altState).getImageKey())) {
if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h);
else {
g.drawImage(ImageCache.getBorderImage(card, canLook), x, y, w, h);
g.drawImage(ImageCache.getBorderImage(card, canshow), x, y, w, h);
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
}
}
else
g.drawImage(image, x, y, w, h);
else {
if (canshow && !ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE).equals(card.getState(altState).getImageKey()))
g.drawImage(image, x, y, w, h);
else // sleeve
g.drawImage(sleeves, x, y, w, h);
}
}
}
CardRenderer.drawFoilEffect(g, card, x, y, w, h, isCurrentCard && canLook && image != ImageCache.defaultImage);
CardRenderer.drawFoilEffect(g, card, x, y, w, h, isCurrentCard && canshow && image != ImageCache.defaultImage);
}
public static void drawDetails(Graphics g, CardView card, GameView gameView, boolean altState, float x, float y, float w, float h) {

View File

@@ -19,9 +19,12 @@ import forge.CachedCardImage;
import forge.Forge;
import forge.FThreads;
import forge.Graphics;
import forge.ImageKeys;
import forge.StaticData;
import forge.assets.FImage;
import forge.assets.FImageComplex;
import forge.assets.FRotatedImage;
import forge.assets.FSkin;
import forge.assets.FSkinColor;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
@@ -46,6 +49,7 @@ import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.MatchController;
import forge.toolbox.FList;
import forge.util.CardTranslation;
import forge.util.Utils;
import org.apache.commons.lang3.StringUtils;
import forge.util.TextBounds;
@@ -430,7 +434,9 @@ public class CardRenderer {
}
}
public static void drawCard(Graphics g, CardView card, float x, float y, float w, float h, CardStackPosition pos, boolean rotate) {
boolean canshow = MatchController.instance.mayView(card) && !ImageKeys.getTokenKey(ImageKeys.MORPH_IMAGE).equals(card.getCurrentState().getImageKey());
Texture image = new RendererCachedCardImage(card, false).getImage();
FImage sleeves = MatchController.getPlayerSleeve(card.getOwner());
float radius = (h - w)/8;
if (image != null) {
@@ -444,7 +450,7 @@ public class CardRenderer {
if (ImageCache.isExtendedArt(card))
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
else {
g.drawfillBorder(3, ImageCache.borderColor(card), x, y, w, h, radius);
g.drawRotatedImage(FSkin.getBorders().get(0), x, y, w, h, x + w / 2, y + h / 2, -90);
g.drawRotatedImage(ImageCache.croppedBorderImage(image), x+radius/2.3f, y+radius/2, w*0.96f, h*0.96f, (x+radius/2.3f) + (w*0.96f) / 2, (y+radius/2) + (h*0.96f) / 2, -90);
}
}
@@ -452,17 +458,21 @@ public class CardRenderer {
g.drawRotatedImage(image, x, y, w, h, x + w / 2, y + h / 2, -90);
}
else {
if (Forge.enableUIMask) {
if (Forge.enableUIMask && canshow) {
if (ImageCache.isExtendedArt(card))
g.drawImage(image, x, y, w, h);
else {
boolean t = (card.getCurrentState().getOriginalColors() != card.getCurrentState().getColors()) || card.getCurrentState().hasChangeColors();
g.drawBorderImage(ImageCache.getBorderImage(card, MatchController.instance.mayView(card)), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors
g.drawBorderImage(ImageCache.getBorderImage(card, canshow), ImageCache.getTint(card), x, y, w, h, t); //tint check for changed colors
g.drawImage(ImageCache.croppedBorderImage(image), x + radius / 2.4f, y + radius / 2, w * 0.96f, h * 0.96f);
}
}
else
g.drawImage(image, x, y, w, h);
else {
if (canshow)
g.drawImage(image, x, y, w, h);
else // draw card back sleeves
g.drawImage(sleeves, x, y, w, h);
}
}
}
drawFoilEffect(g, card, x, y, w, h, false);
@@ -1113,11 +1123,15 @@ public class CardRenderer {
}
public static void drawFoilEffect(Graphics g, CardView card, float x, float y, float w, float h, boolean inZoomer) {
float new_x = x; float new_y = y; float new_w = w; float new_h = h; float radius = (h - w)/8;
if (Forge.enableUIMask) {
new_x += radius/2.4f; new_y += radius/2; new_w = w * 0.96f; new_h = h * 0.96f;
}
if (isPreferenceEnabled(FPref.UI_OVERLAY_FOIL_EFFECT) && MatchController.instance.mayView(card)) {
boolean rotateSplit = isPreferenceEnabled(FPref.UI_ROTATE_SPLIT_CARDS) && card.isSplitCard() && inZoomer;
int foil = card.getCurrentState().getFoilIndex();
if (foil > 0) {
CardFaceSymbols.drawOther(g, String.format("foil%02d", foil), x, y, w, h, rotateSplit);
CardFaceSymbols.drawOther(g, String.format("foil%02d", foil), new_x, new_y, new_w, new_h, rotateSplit);
}
}
}

View File

@@ -20,6 +20,7 @@ import forge.toolbox.FOptionPane;
import forge.toolbox.FTextField;
import forge.toolbox.FEvent.FEventHandler;
import forge.util.Callback;
import forge.util.Localizer;
public class GameEntityPicker extends TabPageScreen<GameEntityPicker> {
private final FOptionPane optionPane;
@@ -73,7 +74,7 @@ public class GameEntityPicker extends TabPageScreen<GameEntityPicker> {
super(caption0 + " (" + items.size() + ")", icon0);
txtSearch = add(new FTextField());
txtSearch.setFont(FSkinFont.get(12));
txtSearch.setGhostText("Search");
txtSearch.setGhostText(Localizer.getInstance().getMessage("lblSearch"));
txtSearch.setChangedHandler(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {

View File

@@ -149,6 +149,7 @@ public class FDeckChooser extends FScreen {
@Override
public void handleEvent(FEvent e) {
if (selectedDeckType != DeckType.STANDARD_COLOR_DECK && selectedDeckType != DeckType.STANDARD_CARDGEN_DECK
&& selectedDeckType != DeckType.PIONEER_CARDGEN_DECK
&& selectedDeckType != DeckType.MODERN_CARDGEN_DECK && selectedDeckType != DeckType.LEGACY_CARDGEN_DECK
&& selectedDeckType != DeckType.VINTAGE_CARDGEN_DECK && selectedDeckType != DeckType.MODERN_COLOR_DECK &&
selectedDeckType != DeckType.COLOR_DECK && selectedDeckType != DeckType.THEME_DECK
@@ -172,6 +173,9 @@ public class FDeckChooser extends FScreen {
else if (selectedDeckType == DeckType.STANDARD_CARDGEN_DECK){
DeckgenUtil.randomSelect(lstDecks);
}
else if (selectedDeckType == DeckType.PIONEER_CARDGEN_DECK){
DeckgenUtil.randomSelect(lstDecks);
}
else if (selectedDeckType == DeckType.MODERN_CARDGEN_DECK){
DeckgenUtil.randomSelect(lstDecks);
}
@@ -296,6 +300,7 @@ public class FDeckChooser extends FScreen {
case RANDOM_CARDGEN_COMMANDER_DECK:
case RANDOM_COMMANDER_DECK:
case MODERN_CARDGEN_DECK:
case PIONEER_CARDGEN_DECK:
case LEGACY_CARDGEN_DECK:
case VINTAGE_CARDGEN_DECK:
case MODERN_COLOR_DECK:
@@ -486,6 +491,7 @@ public class FDeckChooser extends FScreen {
cmbDeckTypes.addItem(DeckType.STANDARD_COLOR_DECK);
if(FModel.isdeckGenMatrixLoaded()) {
cmbDeckTypes.addItem(DeckType.STANDARD_CARDGEN_DECK);
cmbDeckTypes.addItem(DeckType.PIONEER_CARDGEN_DECK);
cmbDeckTypes.addItem(DeckType.MODERN_CARDGEN_DECK);
cmbDeckTypes.addItem(DeckType.LEGACY_CARDGEN_DECK);
cmbDeckTypes.addItem(DeckType.VINTAGE_CARDGEN_DECK);
@@ -698,6 +704,14 @@ public class FDeckChooser extends FScreen {
}
config = ItemManagerConfig.STRING_ONLY;
break;
case PIONEER_CARDGEN_DECK:
maxSelections = 1;
pool= new ArrayList<>();
if(FModel.isdeckGenMatrixLoaded()) {
pool = ArchetypeDeckGenerator.getMatrixDecks(FModel.getFormats().getPioneer(), isAi);
}
config = ItemManagerConfig.STRING_ONLY;
break;
case MODERN_CARDGEN_DECK:
maxSelections = 1;
pool= new ArrayList<>();
@@ -1077,6 +1091,7 @@ public class FDeckChooser extends FScreen {
DeckType.STANDARD_COLOR_DECK,
DeckType.STANDARD_CARDGEN_DECK,
DeckType.MODERN_COLOR_DECK,
DeckType.PIONEER_CARDGEN_DECK,
DeckType.MODERN_CARDGEN_DECK,
DeckType.LEGACY_CARDGEN_DECK,
DeckType.VINTAGE_CARDGEN_DECK,
@@ -1085,6 +1100,7 @@ public class FDeckChooser extends FScreen {
);
if (!FModel.isdeckGenMatrixLoaded()) {
deckTypes.remove(DeckType.STANDARD_CARDGEN_DECK);
deckTypes.remove(DeckType.PIONEER_CARDGEN_DECK);
deckTypes.remove(DeckType.MODERN_CARDGEN_DECK);
deckTypes.remove(DeckType.LEGACY_CARDGEN_DECK);
deckTypes.remove(DeckType.VINTAGE_CARDGEN_DECK);

View File

@@ -13,6 +13,7 @@ import forge.toolbox.FDisplayObject;
import forge.toolbox.FEvent;
import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FTextField;
import forge.util.Localizer;
public class TextSearchFilter<T extends InventoryItem> extends ItemFilter<T> {
@@ -78,10 +79,10 @@ public class TextSearchFilter<T extends InventoryItem> extends ItemFilter<T> {
}
public String getCaption() {
return txtSearch.getGhostText().substring("Search ".length());
return txtSearch.getGhostText().substring((Localizer.getInstance().getMessage("lblSearch") + " ").length());
}
public void setCaption(String caption0) {
txtSearch.setGhostText("Search " + caption0);
txtSearch.setGhostText(Localizer.getInstance().getMessage("lblSearch") + " " + caption0);
}
protected class SearchField extends FTextField {
@@ -89,7 +90,7 @@ public class TextSearchFilter<T extends InventoryItem> extends ItemFilter<T> {
private SearchField() {
setFont(FONT);
setGhostText("Search");
setGhostText(Localizer.getInstance().getMessage("lblSearch"));
setHeight(getDefaultHeight(DEFAULT_FONT)); //set height based on default filter font
}

View File

@@ -37,7 +37,7 @@ public class AvatarSelector extends FScreen {
}
private static final float PADDING = Utils.scale(5);
private static final int COLUMNS = 4;
private static final int COLUMNS = 5;
private final int currentIndex;
private final List<Integer> usedAvatars;

View File

@@ -4,6 +4,7 @@ import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import forge.GuiBase;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
@@ -301,13 +302,20 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
}
/** Saves avatar prefs for players one and two. */
void updateAvatarPrefs() {
void updateAvatarPrefs() {
int pOneIndex = playerPanels.get(0).getAvatarIndex();
int pTwoIndex = playerPanels.get(1).getAvatarIndex();
prefs.setPref(FPref.UI_AVATARS, pOneIndex + "," + pTwoIndex);
prefs.save();
}
void updateSleevePrefs() {
int pOneIndex = playerPanels.get(0).getSleeveIndex();
int pTwoIndex = playerPanels.get(1).getSleeveIndex();
prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex);
prefs.save();
}
/** Updates the avatars from preferences on update. */
private void updatePlayersFromPrefs() {
@@ -320,6 +328,13 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
playerPanels.get(i).setAvatarIndex(avatarIndex);
}
// Sleeves
String[] sleevePrefs = prefs.getPref(FPref.UI_SLEEVES).split(",");
for (int i = 0; i < sleevePrefs.length; i++) {
int sleeveIndex = Integer.parseInt(sleevePrefs[i]);
playerPanels.get(i).setSleeveIndex(sleeveIndex);
}
// Name
String prefName = prefs.getPref(FPref.PLAYER_NAME);
playerPanels.get(0).setPlayerName(StringUtils.isBlank(prefName) ? Localizer.getInstance().getMessage("lblHuman") : prefName);
@@ -334,6 +349,15 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
return usedAvatars;
}
List<Integer> getUsedSleeves() {
List<Integer> usedSleeves = Arrays.asList(-1,-1,-1,-1,-1,-1,-1,-1);
int i = 0;
for (PlayerPanel pp : playerPanels) {
usedSleeves.set(i++, pp.getSleeveIndex());
}
return usedSleeves;
}
List<String> getPlayerNames() {
List<String> names = new ArrayList<>();
for (PlayerPanel pp : playerPanels) {
@@ -350,6 +374,10 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
return playerPanels.get(i).getAvatarIndex();
}
public int getPlayerSleeve(int i) {
return playerPanels.get(i).getSleeveIndex();
}
/////////////////////////////////////////////
//========== Various listeners in build order
@@ -448,6 +476,7 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
DeckType selectedDeckType = deckChooser.getSelectedDeckType();
switch (selectedDeckType){
case STANDARD_CARDGEN_DECK:
case PIONEER_CARDGEN_DECK:
case MODERN_CARDGEN_DECK:
case LEGACY_CARDGEN_DECK:
case VINTAGE_CARDGEN_DECK:
@@ -498,6 +527,9 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
updateVariantSelection();
final boolean allowNetworking = lobby.isAllowNetworking();
GuiBase.setNetworkplay(allowNetworking);
for (int i = 0; i < cbPlayerCount.getSelectedItem(); i++) {
final boolean hasPanel = i < playerPanels.size();
if (i < playerCount) {
@@ -527,8 +559,18 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
final LobbySlotType type = slot.getType();
panel.setType(type);
panel.setPlayerName(slot.getName());
panel.setAvatarIndex(slot.getAvatarIndex());
if (type != LobbySlotType.AI) {
panel.setPlayerName(slot.getName());
panel.setAvatarIndex(slot.getAvatarIndex());
panel.setSleeveIndex(slot.getSleeveIndex());
} else {
//AI: this one overrides the setplayername if blank
if (panel.getPlayerName().isEmpty())
panel.setPlayerName(slot.getName());
//AI: override settings if somehow player changes it for AI
slot.setAvatarIndex(panel.getAvatarIndex());
slot.setSleeveIndex(panel.getSleeveIndex());
}
panel.setTeam(slot.getTeam());
panel.setIsReady(slot.isReady());
panel.setIsDevMode(slot.isDevMode());
@@ -631,6 +673,18 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
}
}
void updateAvatar(final int index, final int avatarIndex) {
if (playerChangeListener != null) {
playerChangeListener.update(index, UpdateLobbyPlayerEvent.avatarUpdate(avatarIndex));
}
}
void updateSleeve(final int index, final int sleeveIndex) {
if (playerChangeListener != null) {
playerChangeListener.update(index, UpdateLobbyPlayerEvent.sleeveUpdate(sleeveIndex));
}
}
void setReady(final int index, final boolean ready) {
if (ready) {
updateDeck(index);
@@ -667,7 +721,7 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
private UpdateLobbyPlayerEvent getSlot(final int index) {
final PlayerPanel panel = playerPanels.get(index);
return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions());
return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), panel.getSleeveIndex(), panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions());
}
public List<PlayerPanel> getPlayerPanels() {

View File

@@ -59,7 +59,8 @@ public class PlayerPanel extends FContainer {
private final FLabel nameRandomiser;
private final FLabel avatarLabel = new FLabel.Builder().opaque(true).iconScaleFactor(0.99f).alphaComposite(1).iconInBackground(true).build();
private int avatarIndex;
private final FLabel sleeveLabel = new FLabel.Builder().opaque(true).iconScaleFactor(0.99f).alphaComposite(1).iconInBackground(true).build();
private int avatarIndex, sleeveIndex;
final Localizer localizer = Localizer.getInstance();
private final FTextField txtPlayerName = new FTextField(localizer.getMessage("lblPlayerName"));
@@ -98,6 +99,7 @@ public class PlayerPanel extends FContainer {
setType(slot.getType());
setPlayerName(slot.getName());
setAvatarIndex(slot.getAvatarIndex());
setSleeveIndex(slot.getSleeveIndex());
devModeSwitch = new FToggleSwitch(localizer.getMessage("lblNormal"), localizer.getMessage("lblDevMode"));
devModeSwitch.setVisible(isNetworkHost());
@@ -189,6 +191,9 @@ public class PlayerPanel extends FContainer {
createAvatar();
add(avatarLabel);
createSleeve();
add(sleeveLabel);
createNameEditor();
add(newLabel(localizer.getMessage("lblName") + ":"));
add(txtPlayerName);
@@ -299,12 +304,16 @@ public class PlayerPanel extends FContainer {
float y = PADDING;
float fieldHeight = txtPlayerName.getHeight();
float avatarSize = 2 * fieldHeight + PADDING;
float sleeveSize = 2 * fieldHeight + PADDING;
float sleeveSizeW = (sleeveSize/4)*3;
float dy = fieldHeight + PADDING;
avatarLabel.setBounds(x, y, avatarSize, avatarSize);
x += avatarSize + PADDING;
sleeveLabel.setBounds(x, y, sleeveSizeW, sleeveSize);
x += sleeveSizeW + PADDING;
float w = width - x - fieldHeight - 2 * PADDING;
txtPlayerName.setBounds(x, y, w, fieldHeight);
txtPlayerName.setBounds(x, y, w, fieldHeight); //add space for card back
x += w + PADDING;
nameRandomiser.setBounds(x, y, fieldHeight, fieldHeight);
@@ -312,8 +321,8 @@ public class PlayerPanel extends FContainer {
humanAiSwitch.setSize(humanAiSwitch.getAutoSizeWidth(fieldHeight), fieldHeight);
x = width - humanAiSwitch.getWidth() - PADDING;
humanAiSwitch.setPosition(x, y);
w = x - avatarSize - 3 * PADDING;
x = avatarSize + 2 * PADDING;
w = x - (avatarSize+sleeveSizeW+PADDING) - 3 * PADDING;
x = (avatarSize+sleeveSizeW+PADDING) + 2 * PADDING;
if (cbArchenemyTeam.isVisible()) {
cbArchenemyTeam.setBounds(x, y, w, fieldHeight);
}
@@ -411,6 +420,7 @@ public class PlayerPanel extends FContainer {
//update may edit in-case it changed as a result of the AI change
setMayEdit(screen.getLobby().mayEdit(index));
setAvatarIndex(slot.getAvatarIndex());
setSleeveIndex(slot.getSleeveIndex());
setPlayerName(slot.getName());
}
}
@@ -470,6 +480,7 @@ public class PlayerPanel extends FContainer {
setAvatarIndex(result);
if (index < 2) {
screen.updateAvatar(index, result);
screen.updateAvatarPrefs();
}
if (allowNetworking) {
@@ -480,6 +491,26 @@ public class PlayerPanel extends FContainer {
}
};
private FEventHandler sleeveCommand = new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
SleevesSelector.show(getPlayerName(), sleeveIndex, screen.getUsedSleeves(), new Callback<Integer>() {
@Override
public void run(Integer result) {
setSleeveIndex(result);
if (index < 2) {
screen.updateSleeve(index, result);
screen.updateSleevePrefs();
}
if (allowNetworking) {
screen.firePlayerChangeListener(index);
}
}
});
}
};
public void setDeckSelectorButtonText(String text) {
btnDeck.setText(text);
}
@@ -664,6 +695,17 @@ public class PlayerPanel extends FContainer {
avatarLabel.setCommand(avatarCommand);
}
private void createSleeve() {
String[] currentPrefs = prefs.getPref(FPref.UI_SLEEVES).split(",");
if (index < currentPrefs.length) {
setSleeveIndex(Integer.parseInt(currentPrefs[index]));
}
else {
setSleeveIndex(SleevesSelector.getRandomSleeves(screen.getUsedSleeves()));
}
sleeveLabel.setCommand(sleeveCommand);
}
public void setAvatarIndex(int newAvatarIndex) {
avatarIndex = newAvatarIndex;
if (avatarIndex != -1) {
@@ -674,10 +716,24 @@ public class PlayerPanel extends FContainer {
}
}
public void setSleeveIndex(int newSleeveIndex) {
sleeveIndex = newSleeveIndex;
if (sleeveIndex != -1) {
sleeveLabel.setIcon(new FTextureRegionImage(FSkin.getSleeves().get(newSleeveIndex)));
}
else {
sleeveLabel.setIcon(null);
}
}
public int getAvatarIndex() {
return avatarIndex;
}
public int getSleeveIndex() {
return sleeveIndex;
}
public void setPlayerName(String string) {
txtPlayerName.setText(string);
}
@@ -759,6 +815,7 @@ public class PlayerPanel extends FContainer {
if (mayEdit == mayEdit0) { return; }
mayEdit = mayEdit0;
avatarLabel.setEnabled(mayEdit);
sleeveLabel.setEnabled(mayEdit);
txtPlayerName.setEnabled(mayEdit);
nameRandomiser.setEnabled(mayEdit);
humanAiSwitch.setEnabled(mayEdit);

View File

@@ -0,0 +1,119 @@
package forge.screens.constructed;
import forge.Forge;
import forge.assets.FImage;
import forge.assets.FSkin;
import forge.assets.FSkinImage;
import forge.assets.FTextureRegionImage;
import forge.screens.FScreen;
import forge.toolbox.FDisplayObject;
import forge.toolbox.FEvent;
import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FLabel;
import forge.toolbox.FScrollPane;
import forge.util.Callback;
import forge.util.MyRandom;
import forge.util.Utils;
import java.util.List;
import java.util.Map;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Align;
public class SleevesSelector extends FScreen {
public static int getRandomSleeves(List<Integer> usedSleeves) {
int random = 0;
do {
random = MyRandom.getRandom().nextInt(FSkin.getSleeves().size());
} while (usedSleeves.contains(random));
return random;
}
public static void show(final String playerName, final int currentIndex0, final List<Integer> usedSleeves0, final Callback<Integer> callback0) {
SleevesSelector selector = new SleevesSelector(playerName, currentIndex0, usedSleeves0, callback0);
Forge.openScreen(selector);
}
private static final float PADDING = Utils.scale(5);
private static final int COLUMNS = 5;
private final int currentIndex;
private final List<Integer> usedSleeves;
private final Callback<Integer> callback;
private final FScrollPane scroller = new FScrollPane() {
@Override
protected ScrollBounds layoutAndGetScrollBounds(float visibleWidth, float visibleHeight) {
int rowCount = 0;
float x = PADDING;
float y = PADDING;
float labelSize = (visibleWidth - (COLUMNS + 1) * PADDING) / COLUMNS;
for (FDisplayObject lbl : scroller.getChildren()) {
if (rowCount == COLUMNS) { //wrap to next line
x = PADDING;
y += labelSize + PADDING;
rowCount = 0;
}
lbl.setBounds(x, y, labelSize, labelSize);
x += labelSize + PADDING;
rowCount++;
}
return new ScrollBounds(visibleWidth, y + labelSize + PADDING);
}
};
private SleevesSelector(final String playerName, final int currentIndex0, final List<Integer> usedSleeves0, final Callback<Integer> callback0) {
super("Select Sleeves for " + playerName);
currentIndex = currentIndex0;
usedSleeves = usedSleeves0;
callback = callback0;
//add label for selecting random sleeves first
addSleevesLabel(FSkinImage.UNKNOWN, -1);
//add label for currently selected sleeves next
final Map<Integer, TextureRegion> sleeveMap = FSkin.getSleeves();
addSleevesLabel(new FTextureRegionImage(sleeveMap.get(currentIndex)), currentIndex);
//add label for remaining sleeves
for (final Integer i : sleeveMap.keySet()) {
if (currentIndex != i) {
addSleevesLabel(new FTextureRegionImage(sleeveMap.get(i)), i);
}
}
add(scroller);
}
private void addSleevesLabel(final FImage img, final int index) {
final FLabel lbl = new FLabel.Builder().icon(img).iconScaleFactor(0.99f).align(Align.center)
.iconInBackground(true).selectable(true).selected(currentIndex == index)
.build();
if (index == -1) {
lbl.setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
callback.run(getRandomSleeves(usedSleeves));
Forge.back();
}
});
}
else {
lbl.setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
callback.run(index);
Forge.back();
}
});
}
scroller.add(lbl);
}
@Override
protected void doLayout(float startY, float width, float height) {
scroller.setBounds(0, startY, width, height - startY);
}
}

View File

@@ -23,15 +23,18 @@ import forge.toolbox.FTextArea;
import forge.toolbox.GuiChoose;
import forge.toolbox.ListChooser;
import forge.util.Callback;
import forge.util.Localizer;
import forge.util.Utils;
public class NewGauntletScreen extends LaunchScreen {
private static final float PADDING = Utils.scale(10);
private final Localizer localizer = Localizer.getInstance();
private final FTextArea lblDesc = add(new FTextArea(false,
"In Gauntlet mode, you select a deck and play against multiple opponents.\n\n" +
"Configure how many opponents you wish to face and what decks or types of decks they will play.\n\n" +
"Then, try to beat all AI opponents without losing a match."));
localizer.getMessage("lblGauntletText1") + "\n\n" +
localizer.getMessage("lblGauntletText2") + "\n\n" +
localizer.getMessage("lblGauntletText3")));
public NewGauntletScreen() {
super(null, NewGameMenu.getMenu());
@@ -51,44 +54,41 @@ public class NewGauntletScreen extends LaunchScreen {
@Override
protected void startMatch() {
GuiChoose.oneOrNone("Select a Gauntlet Type", new String[] {
"Quick Gauntlet",
"Custom Gauntlet",
"Gauntlet Contest",
GuiChoose.oneOrNone(localizer.getMessage("lblSelectGauntletType"), new String[] {
localizer.getMessage("lblQuickGauntlet"),
localizer.getMessage("lblCustomGauntlet"),
localizer.getMessage("lblGauntletContest"),
}, new Callback<String>() {
@Override
public void run(String result) {
if (result == null) { return; }
switch (result) {
case "Quick Gauntlet":
if (localizer.getMessage("lblQuickGauntlet").equals(result)) {
createQuickGauntlet();
break;
case "Custom Gauntlet":
} else if(localizer.getMessage("lblCustomGauntlet").equals(result)) {
createCustomGauntlet();
break;
default:
} else {
createGauntletContest();
break;
}
}
});
}
private void createQuickGauntlet() {
GuiChoose.getInteger("How many opponents are you willing to face?", 3, 50, new Callback<Integer>() {
GuiChoose.getInteger(localizer.getMessage("lblHowManyOpponents"), 3, 50, new Callback<Integer>() {
@Override
public void run(final Integer numOpponents) {
if (numOpponents == null) { return; }
ListChooser<DeckType> chooser = new ListChooser<>(
"Choose allowed deck types for opponents", 0, 11, Arrays.asList(DeckType.CUSTOM_DECK,
localizer.getMessage("lblChooseAllowedDeckTypeOpponents"), 0, 11, Arrays.asList(DeckType.CUSTOM_DECK,
DeckType.PRECONSTRUCTED_DECK,
DeckType.QUEST_OPPONENT_DECK,
DeckType.COLOR_DECK,
DeckType.STANDARD_COLOR_DECK,
DeckType.STANDARD_CARDGEN_DECK,
DeckType.MODERN_COLOR_DECK,
DeckType.PIONEER_CARDGEN_DECK,
DeckType.MODERN_CARDGEN_DECK,
DeckType.LEGACY_CARDGEN_DECK,
DeckType.VINTAGE_CARDGEN_DECK,
@@ -99,7 +99,7 @@ public class NewGauntletScreen extends LaunchScreen {
return;
}
FDeckChooser.promptForDeck("Select Your Deck", GameType.Gauntlet, false, new Callback<Deck>() {
FDeckChooser.promptForDeck(localizer.getMessage("lblSelectYourDeck"), GameType.Gauntlet, false, new Callback<Deck>() {
@Override
public void run(Deck userDeck) {
if (userDeck == null) {
@@ -118,7 +118,7 @@ public class NewGauntletScreen extends LaunchScreen {
}
private void createCustomGauntlet() {
GuiChoose.getInteger("How many opponents are you willing to face?", 3, 50, new Callback<Integer>() {
GuiChoose.getInteger(localizer.getMessage("lblHowManyOpponents"), 3, 50, new Callback<Integer>() {
@Override
public void run(final Integer numOpponents) {
if (numOpponents == null) { return; }
@@ -132,7 +132,7 @@ public class NewGauntletScreen extends LaunchScreen {
private void promptForAiDeck(final GauntletData gauntlet, final int numOpponents) {
final int opponentNum = gauntlet.getDecks().size() + 1;
FDeckChooser.promptForDeck("Select Deck for Opponent " + opponentNum + " / " + numOpponents, GameType.Gauntlet, true, new Callback<Deck>() {
FDeckChooser.promptForDeck(localizer.getMessage("lblSelectDeckForOpponent") + " " + opponentNum + " / " + numOpponents, GameType.Gauntlet, true, new Callback<Deck>() {
@Override
public void run(Deck aiDeck) {
if (aiDeck == null) { return; }
@@ -145,7 +145,7 @@ public class NewGauntletScreen extends LaunchScreen {
}
else {
//once all ai decks have been selected, prompt for user deck
FDeckChooser.promptForDeck("Select Your Deck", GameType.Gauntlet, false, new Callback<Deck>() {
FDeckChooser.promptForDeck(localizer.getMessage("lblSelectYourDeck"), GameType.Gauntlet, false, new Callback<Deck>() {
@Override
public void run(Deck userDeck) {
if (userDeck == null) { return; }
@@ -170,12 +170,12 @@ public class NewGauntletScreen extends LaunchScreen {
}
}
GuiChoose.oneOrNone("Select Gauntlet Contest", contests, new Callback<GauntletData>() {
GuiChoose.oneOrNone(localizer.getMessage("lblSelectGauntletContest"), contests, new Callback<GauntletData>() {
@Override
public void run(final GauntletData contest) {
if (contest == null) { return; }
FDeckChooser.promptForDeck("Select Your Deck", GameType.Gauntlet, false, new Callback<Deck>() {
FDeckChooser.promptForDeck(localizer.getMessage("lblSelectYourDeck"), GameType.Gauntlet, false, new Callback<Deck>() {
@Override
public void run(final Deck userDeck) {
if (userDeck == null) { return; }

View File

@@ -19,15 +19,13 @@ import forge.toolbox.FEvent.FEventHandler;
import forge.util.Localizer;
public class LoadGameMenu extends FPopupMenu {
final static Localizer localizer = Localizer.getInstance();
public enum LoadGameScreen {
BoosterDraft(localizer.getMessage("lblBoosterDraft"), FSkinImage.HAND, LoadDraftScreen.class),
SealedDeck(localizer.getMessage("lblSealedDeck"), FSkinImage.PACK, LoadSealedScreen.class),
QuestMode(localizer.getMessage("lblQuestMode"), FSkinImage.QUEST_ZEP, LoadQuestScreen.class),
PlanarConquest(localizer.getMessage("lblPlanarConquest"), FSkinImage.MULTIVERSE, LoadConquestScreen.class),
Gauntlet(localizer.getMessage("lblGauntlet"), FSkinImage.ALPHASTRIKE, LoadGauntletScreen.class);
BoosterDraft("Booster Draft", FSkinImage.HAND, LoadDraftScreen.class),
SealedDeck("Sealed Deck", FSkinImage.PACK, LoadSealedScreen.class),
QuestMode("Quest Mode", FSkinImage.QUEST_ZEP, LoadQuestScreen.class),
PlanarConquest("Planar Conquest", FSkinImage.MULTIVERSE, LoadConquestScreen.class),
Gauntlet("Gauntlet", FSkinImage.ALPHASTRIKE, LoadGauntletScreen.class);
private final FMenuItem item;
private final Class<? extends FScreen> screenClass;
private FScreen screen;
@@ -47,7 +45,7 @@ public class LoadGameMenu extends FPopupMenu {
if (screen == null) { //don't initialize screen until it's opened the first time
try {
screen = screenClass.newInstance();
screen.setHeaderCaption(localizer.getMessage("lblLoadGame") + " - " + item.getText());
screen.setHeaderCaption(Localizer.getInstance().getMessage("lblLoadGame") + " - " + item.getText());
}
catch (Exception e) {
e.printStackTrace();

View File

@@ -18,6 +18,7 @@ import forge.toolbox.FOptionPane;
import forge.toolbox.FTextArea;
import forge.toolbox.GuiChoose;
import forge.util.Callback;
import forge.util.Localizer;
import forge.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -28,10 +29,9 @@ public class PuzzleScreen extends LaunchScreen {
private static final float PADDING = Utils.scale(10);
private final FTextArea lblDesc = add(new FTextArea(false,
"Puzzle Mode loads in a puzzle that you have to win in a predetermined time/way.\n\n" +
"To begin, press the Start button below, then select a puzzle from a list.\n\n" +
"Your objective will be displayed in a pop-up window when the puzzle starts and also " +
"specified on a special effect card which will be placed in your command zone."));
Localizer.getInstance().getMessage("lblPuzzleText1") + "\n\n" +
Localizer.getInstance().getMessage("lblPuzzleText2") + "\n\n" +
Localizer.getInstance().getMessage("lblPuzzleText3")));
public PuzzleScreen() {
super(null, NewGameMenu.getMenu());
@@ -54,10 +54,10 @@ public class PuzzleScreen extends LaunchScreen {
final ArrayList<Puzzle> puzzles = PuzzleIO.loadPuzzles();
Collections.sort(puzzles);
GuiChoose.one("Choose a puzzle", puzzles, new Callback<Puzzle>() {
GuiChoose.one(Localizer.getInstance().getMessage("lblChooseAPuzzle"), puzzles, new Callback<Puzzle>() {
@Override
public void run(final Puzzle chosen) {
LoadingOverlay.show("Loading the puzzle...", new Runnable() {
LoadingOverlay.show(Localizer.getInstance().getMessage("lblLoadingThePuzzle"), new Runnable() {
@Override
public void run() {
// Load selected puzzle

View File

@@ -30,6 +30,7 @@ import forge.match.HostedMatch;
import forge.model.FModel;
import forge.player.GamePlayerUtil;
import forge.toolbox.FComboBox;
import forge.util.Localizer;
import forge.util.gui.SGuiChoose;
import java.util.ArrayList;
import java.util.List;
@@ -37,20 +38,20 @@ import java.util.List;
public class LoadDraftScreen extends LaunchScreen {
private final DeckManager lstDecks = add(new DeckManager(GameType.Draft));
private final FLabel lblTip = add(new FLabel.Builder()
.text("Double-tap to edit deck (Long-press to view)")
.text(Localizer.getInstance().getMessage("lblDoubleTapToEditDeck"))
.textColor(FLabel.INLINE_LABEL_COLOR)
.align(Align.center).font(FSkinFont.get(12)).build());
private final FSkinFont GAME_MODE_FONT= FSkinFont.get(12);
private final FLabel lblMode = add(new FLabel.Builder().text("Mode:").font(GAME_MODE_FONT).build());
private final FLabel lblMode = add(new FLabel.Builder().text(Localizer.getInstance().getMessage("lblMode")).font(GAME_MODE_FONT).build());
private final FComboBox<String> cbMode = add(new FComboBox<>());
public LoadDraftScreen() {
super(null, LoadGameMenu.getMenu());
cbMode.setFont(GAME_MODE_FONT);
cbMode.addItem("Gauntlet");
cbMode.addItem("Single Match");
cbMode.addItem(Localizer.getInstance().getMessage("lblGauntlet"));
cbMode.addItem(Localizer.getInstance().getMessage("lblSingleMatch"));
lstDecks.setup(ItemManagerConfig.DRAFT_DECKS);
lstDecks.setItemActivateHandler(new FEventHandler() {
@@ -98,17 +99,18 @@ public class LoadDraftScreen extends LaunchScreen {
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
Localizer localizer = Localizer.getInstance();
final DeckProxy humanDeck = lstDecks.getSelectedItem();
if (humanDeck == null) {
FOptionPane.showErrorDialog("You must select an existing deck or build a deck from a new booster draft game.", "No Deck");
FOptionPane.showErrorDialog(localizer.getMessage("lblYouMustSelectExistingDeck"), localizer.getMessage("lblNoDeck"));
return;
}
// TODO: if booster draft tournaments are supported in the future, add the possibility to choose them here
final boolean gauntlet = cbMode.getSelectedItem().equals("Gauntlet");
final boolean gauntlet = cbMode.getSelectedItem().equals(localizer.getMessage("lblGauntlet"));
if (gauntlet) {
final Integer rounds = SGuiChoose.getInteger("How many opponents are you willing to face?",
final Integer rounds = SGuiChoose.getInteger(localizer.getMessage("lblHowManyOpponents"),
1, FModel.getDecks().getDraft().get(humanDeck.getName()).getAiDecks().size());
if (rounds == null) {
return;
@@ -121,7 +123,7 @@ public class LoadDraftScreen extends LaunchScreen {
return;
}
LoadingOverlay.show("Loading new game...", new Runnable() {
LoadingOverlay.show(localizer.getMessage("lblLoadingNewGame"), new Runnable() {
@Override
public void run() {
FModel.getGauntletMini().resetGauntletDraft();
@@ -131,7 +133,7 @@ public class LoadDraftScreen extends LaunchScreen {
}
});
} else {
final Integer aiIndex = SGuiChoose.getInteger("Which opponent would you like to face?",
final Integer aiIndex = SGuiChoose.getInteger(localizer.getMessage("lblWhichOpponentWouldYouLikeToFace"),
1, FModel.getDecks().getDraft().get(humanDeck.getName()).getAiDecks().size());
if (aiIndex == null) {
return; // Cancel was pressed
@@ -146,7 +148,7 @@ public class LoadDraftScreen extends LaunchScreen {
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
LoadingOverlay.show("Loading new game...", new Runnable() {
LoadingOverlay.show(localizer.getMessage("lblLoadingNewGame"), new Runnable() {
@Override
public void run() {
if (!checkDeckLegality(humanDeck)) {
@@ -177,7 +179,7 @@ public class LoadDraftScreen extends LaunchScreen {
if (FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) {
String errorMessage = GameType.Draft.getDeckFormat().getDeckConformanceProblem(humanDeck.getDeck());
if (errorMessage != null) {
FOptionPane.showErrorDialog("Your deck " + errorMessage + "\nPlease edit or choose a different deck.", "Invalid Deck");
FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblInvalidDeckDesc").replace("%n", errorMessage), Localizer.getInstance().getMessage("lblInvalidDeck"));
return false;
}
}

View File

@@ -11,6 +11,7 @@ import forge.screens.LoadingOverlay;
import forge.screens.home.NewGameMenu;
import forge.toolbox.FLabel;
import forge.toolbox.FTextArea;
import forge.util.Localizer;
import forge.util.ThreadUtil;
import forge.util.Utils;
import forge.util.gui.SGuiChoose;
@@ -19,9 +20,9 @@ public class NewDraftScreen extends LaunchScreen {
private static final float PADDING = Utils.scale(10);
private final FTextArea lblDesc = add(new FTextArea(false,
"In Draft mode, three booster packs are rotated around eight players.\n\n" +
"Build a deck from the cards you choose. The AI will do the same.\n\n" +
"Then, play against any number of the AI opponents."));
Localizer.getInstance().getMessage("lblDraftText1") + "\n\n" +
Localizer.getInstance().getMessage("lblDraftText2") + "\n\n" +
Localizer.getInstance().getMessage("lblDraftText3")));
public NewDraftScreen() {
super(null, NewGameMenu.getMenu());
@@ -44,7 +45,7 @@ public class NewDraftScreen extends LaunchScreen {
ThreadUtil.invokeInGameThread(new Runnable() { //must run in game thread to prevent blocking UI thread
@Override
public void run() {
final LimitedPoolType poolType = SGuiChoose.oneOrNone("Choose Draft Format", LimitedPoolType.values());
final LimitedPoolType poolType = SGuiChoose.oneOrNone(Localizer.getInstance().getMessage("lblChooseDraftFormat"), LimitedPoolType.values());
if (poolType == null) { return; }
final BoosterDraft draft = BoosterDraft.createDraft(poolType);
@@ -53,7 +54,7 @@ public class NewDraftScreen extends LaunchScreen {
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
LoadingOverlay.show("Loading new draft...", new Runnable() {
LoadingOverlay.show(Localizer.getInstance().getMessage("lblLoadingNewDraft"), new Runnable() {
@Override
public void run() {
Forge.openScreen(new DraftingProcessScreen(draft, EditorType.Draft, null));

View File

@@ -12,6 +12,7 @@ import forge.screens.LaunchScreen;
import forge.screens.home.NewGameMenu;
import forge.toolbox.FLabel;
import forge.toolbox.FTextArea;
import forge.util.Localizer;
import forge.util.ThreadUtil;
import forge.util.Utils;
@@ -19,9 +20,9 @@ public class NewSealedScreen extends LaunchScreen {
private static final float PADDING = Utils.scale(10);
private final FTextArea lblDesc = add(new FTextArea(false,
"In Sealed mode, you build a deck from booster packs (maximum 10).\n\n" +
"Build a deck from the cards you receive. A number of AI opponents will do the same.\n\n" +
"Then, play against each of the AI opponents."));
Localizer.getInstance().getMessage("lblSealedText2") + "\n\n" +
Localizer.getInstance().getMessage("lblSealedText3") + "\n\n" +
Localizer.getInstance().getMessage("lblSealedText4")));
public NewSealedScreen() {
super(null, NewGameMenu.getMenu());

View File

@@ -7,6 +7,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import forge.FThreads;
import forge.assets.FSkinImage;
import forge.util.Localizer;
import org.apache.commons.lang3.StringUtils;
@@ -68,6 +70,8 @@ public class MatchController extends AbstractGuiGame {
private static final Map<String, FImage> avatarImages = new HashMap<>();
private static final Map<String, FImage> sleeveImages = new HashMap<>();
private static HostedMatch hostedMatch;
private static MatchScreen view;
@@ -100,12 +104,34 @@ public class MatchController extends AbstractGuiGame {
return avatar;
}
public static FImage getPlayerSleeve(final PlayerView p) {
if (p == null)
return FSkinImage.UNKNOWN;
final String lp = p.getLobbyPlayerName();
FImage sleeve = sleeveImages.get(lp);
if (sleeve == null) {
sleeve = new FTextureRegionImage(FSkin.getSleeves().get(p.getSleeveIndex()));
}
return sleeve;
}
@Override
public void refreshCardDetails(final Iterable<CardView> cards) {
//ensure cards appear in the correct row of the field
/*//ensure cards appear in the correct row of the field
for (final VPlayerPanel pnl : view.getPlayerPanels().values()) {
pnl.getField().update();
}
}*/
}
@Override
public void refreshField() {
if(getGameView() == null)
return;
if(getGameView().getPhase() == null)
return;
if (getGameView().getPhase().phaseforUpdateField())
for (final VPlayerPanel pnl : view.getPlayerPanels().values())
pnl.getField().update();
}
public boolean hotSeatMode() {
@@ -363,6 +389,42 @@ public class MatchController extends AbstractGuiGame {
}
}
@Override
public void setSelectables(final Iterable<CardView> cards) {
super.setSelectables(cards);
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
for (final PlayerView p : getGameView().getPlayers()) {
if ( p.getCards(ZoneType.Battlefield) != null ) {
updateCards(p.getCards(ZoneType.Battlefield));
}
if ( p.getCards(ZoneType.Hand) != null ) {
updateCards(p.getCards(ZoneType.Hand));
}
}
}
});
}
@Override
public void clearSelectables() {
super.clearSelectables();
// update zones on tabletop and floating zones - non-selectable cards may be rendered differently
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
for (final PlayerView p : getGameView().getPlayers()) {
if ( p.getCards(ZoneType.Battlefield) != null ) {
updateCards(p.getCards(ZoneType.Battlefield));
}
if ( p.getCards(ZoneType.Hand) != null ) {
updateCards(p.getCards(ZoneType.Hand));
}
}
}
});
}
@Override
public void afterGameEnd() {
Forge.back();

View File

@@ -376,6 +376,14 @@ public class MatchScreen extends FScreen {
TargetingOverlay.drawArrow(g, blocker, attacker);
}
}
//player
if (is4Player() || is3Player()) {
int numplayers = is3Player() ? 3 : 4;
for (final PlayerView p : game.getPlayers()) {
if (combat.getAttackersOf(p).contains(attacker))
TargetingOverlay.drawArrow(g, attacker, p, numplayers);
}
}
}
}
@@ -395,7 +403,7 @@ public class MatchScreen extends FScreen {
return getActivePrompt().getBtnCancel().trigger(); //trigger Cancel if can't trigger OK
case Keys.ESCAPE:
if (!FModel.getPreferences().getPrefBoolean(FPref.UI_ALLOW_ESC_TO_END_TURN)) {
if (getActivePrompt().getBtnCancel().getText().equals("End Turn")) {
if (getActivePrompt().getBtnCancel().getText().equals(Localizer.getInstance().getMessage("lblEndTurn"))) {
return false;
}
}

View File

@@ -79,6 +79,11 @@ public class TargetingOverlay {
CardAreaPanel.get(endCard).getTargetingArrowOrigin(),
connects);
}
public static void drawArrow(Graphics g, CardView startCard, PlayerView targetPlayer, int numplayers) {
drawArrow(g, CardAreaPanel.get(startCard).getTargetingArrowOrigin(),
MatchController.getView().getPlayerPanel(targetPlayer).getAvatar().getTargetingArrowOrigin(numplayers),
ArcConnection.FoesAttacking);
}
public static void drawArrow(Graphics g, Vector2 start, CardView targetCard, ArcConnection connects) {
drawArrow(g, start,
CardAreaPanel.get(targetCard).getTargetingArrowOrigin(),

View File

@@ -45,10 +45,15 @@ public class VAvatar extends FDisplayObject {
}
public Vector2 getTargetingArrowOrigin() {
return getTargetingArrowOrigin(2);
}
public Vector2 getTargetingArrowOrigin(int numplayers) {
Vector2 origin = new Vector2(screenPos.x, screenPos.y);
origin.x += WIDTH * 0.75f;
if (origin.y < MatchController.getView().getHeight() / 2) {
float modx = numplayers > 2 ? 0.25f : 0.75f;
origin.x += WIDTH * modx;
if (origin.y < MatchController.getView().getHeight() / numplayers) {
origin.y += HEIGHT * 0.75f; //target bottom right corner if on top half of screen
}
else {

View File

@@ -53,21 +53,24 @@ public class ControlWinLose {
view.hide();
saveOptions();
MatchController.getHostedMatch().continueMatch();
try { MatchController.getHostedMatch().continueMatch();
} catch (NullPointerException e) {}
}
/** Action performed when "restart" button is pressed in default win/lose UI. */
public void actionOnRestart() {
view.hide();
saveOptions();
MatchController.getHostedMatch().restartMatch();
try { MatchController.getHostedMatch().restartMatch();
} catch (NullPointerException e) {}
}
/** Action performed when "quit" button is pressed in default win/lose UI. */
public void actionOnQuit() {
// Reset other stuff
saveOptions();
MatchController.getHostedMatch().endCurrentGame();
try { MatchController.getHostedMatch().endCurrentGame();
} catch (NullPointerException e) {}
view.hide();
}

View File

@@ -302,8 +302,8 @@ public class SettingsPage extends TabPage<SettingsScreen> {
localizer.getMessage("nlDisableCardEffect")),
4);
lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_BORDER_MASKING,
"Enable Round Border Mask",
"When enabled, the card corners are rounded (Preferably Card with Full Borders)."){
localizer.getMessage("lblEnableRoundBorder"),
localizer.getMessage("nlEnableRoundBorder")){
@Override
public void select() {
super.select();
@@ -312,8 +312,8 @@ public class SettingsPage extends TabPage<SettingsScreen> {
}
},4);
lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_PRELOAD_EXTENDED_ART,
"Preload Extended Art Cards",
"When enabled, Preloads Extended Art Cards to Cache on Startup."){
localizer.getMessage("lblPreloadExtendedArtCards"),
localizer.getMessage("nlPreloadExtendedArtCards")){
@Override
public void select() {
super.select();
@@ -322,8 +322,8 @@ public class SettingsPage extends TabPage<SettingsScreen> {
}
},4);
lstSettings.addItem(new BooleanSetting(FPref.UI_SHOW_FPS,
"Show FPS Display",
"When enabled, show the FPS Display (Experimental)."){
localizer.getMessage("lblShowFPSDisplay"),
localizer.getMessage("nlShowFPSDisplay")){
@Override
public void select() {
super.select();

View File

@@ -1 +1,84 @@
This file is automatically updated by our release bot on Discord, Blacksmith. It is created from the files present in the 'release-files' directory. Please do not hand-edit this file if using the bot to perform a release, as your changes will be overwritten.
Forge: 11/11/2019 ver 1.6.30
19439 cards in total.
--------------
Release Notes:
--------------
- Bug fixes -
As always, this release of Forge features an assortment of bug fixes and improvements based on user feedback during the previous release run.
-------------
Known Issues:
-------------
Known issues are here: https://git.cardforge.org/core-developers/forge/issues
Feel free to report your own there if you have any.
-------------
Installation:
-------------
The Forge archive includes a MANUAL.txt file and we ask that you spend a few minutes reading this file as it contains some information that may prove useful. We do tend to update this file at times and you should quickly read this file and look for new information for each and every new release. Thank you.
The archive format used for the Forge distribution is ".tar.bz2". There are utilities for Windows, Mac OS and the various *nix's that can be used to extract/decompress these ".tar.bz2" archives. We recommend that you extract/decompress the Forge archive into a new and unused folder.
Some people use the Windows application 7zip. This utility can be found at http://www.7-zip.org/download.html. Mac users can double click on the archive and the application Archive Utility will launch and extract the archive. Mac users do not need to download a separate utility.
Once the Forge archive has been decompressed you should then be able to launch Forge by using the included launcher. Launching Forge by double clicking on the forge jar file in the past caused a java heap space error. Forge's memory requirements have increased over time and the launchers increase the java heap space available to Forge. Currently you can launch Forge by double clicking on the forge jar file without a java heap space error but this is likely to change as we add in more sounds, icons, etc.
- The Mac OS application version -
We provide separate macOS builds of desktop and mobile (backported) Forge packaged as native Mac applications. Please check the relevant thread for details and download links.
- Online Multiplayer -
For local network play you should only need two systems running Forge. One to host and one to join and play. For remote (over the Internet) play you will need to ensure that the port used (36743 by default) is forwarded to the hosting machine.
--------------------
Active Contributors:
--------------------
Agetian
apantel
Austinio7116
Churrufli
DrDev
excessum
Flair
Gos
Hanmac
Indigo Dragon
Jamin Collins
kevlahnota
klaxnek
KrazyTheFox
leriomaggio
Luke
Marek14
mcrawford620
Meerkov
Myrd
nefigah
OgreBattlecruiser
pfps
Ryan1729
Seravy
Sirspud
Sloth
slyfox7777777
Sol
Swordshine
tjtillman
tojammot
torridus
Xyx
Zuchinni
(If you think your name should be on this list, add it with your next contribution)
(Quest icons used created by Teekatas, from his Legendora set http://raindropmemory.deviantart.com)
(Thanks to the XMage team for permission to use their targeting arrows.)
(Thanks to http://www.freesound.org/browse/ for providing some sound files.)

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>forge</artifactId>
<groupId>forge</groupId>
<version>1.6.30-SNAPSHOT</version>
<version>1.6.30</version>
</parent>
<artifactId>forge-gui</artifactId>
@@ -39,28 +39,33 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-android</version>
<version>28.1-android</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.25.Final</version>
<version>4.1.43.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.fourthline.cling</groupId>
@@ -72,6 +77,11 @@
<artifactId>slf4j-simple</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>elsa</artifactId>
<version>3.0.0-M7</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,6 +1,8 @@
#Add one announcement per line
Throne of Eldraine pre-release!
Updated Libgdx from 1.5.5 to 1.9.10 ([url=https://github.com/libgdx/libgdx/blob/master/CHANGES]detailed changes are here[/url]).
Throne of Eldraine fixes
Pioneer is here!
Some fancy looking UI stuff was added. Card Sleeves and Keyword Icons
Continued work on Translations
[b]Forge now requires Java 8 (or newer). You will not be able to start the game if you are not yet running Java 8.[/b]
For some reason Oracle hates Forge and version 1.8.0_211 does bad things with Forge for unknown reasons. Downgrade to 202 for a beter time.
https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase8-2177648.html

View File

@@ -10,6 +10,7 @@ Hanmac
Indigo Dragon
Jamin Collins
kevlahnota
klaxnek
KrazyTheFox
leriomaggio
Luke

View File

@@ -9,6 +9,6 @@ SVar:BuffedBy:Permanent.Snow
SVar:NoZeroToughnessAI:True
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When CARDNAME enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.
SVar:TrigTap:DB$ Tap | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Choose target creature an opponent controls. | SubAbility$ DBPump
SVar:DBPump:DB$ CantUntapTurn | Defined$ Targeted
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True
SVar:PlayMain1:TRUE
Oracle:Trample\nAbominable Treefolk's power and toughness are each equal to the number of snow permanents you control.\nWhen Abominable Treefolk enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.

View File

@@ -3,7 +3,7 @@ ManaCost:3 U
Types:Instant
K:Devoid
A:SP$ Tap | Cost$ 3 U | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Choose target creature | ValidTgts$ Creature | SubAbility$ TrigPump | SpellDescription$ Tap up to two target creatures.
SVar:TrigPump:DB$ CantUntapTurn | Defined$ Targeted | SubAbility$ DBToken | SpellDescription$ Those creatures don't untap during their controller's next untap step.
SVar:TrigPump:DB$Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True | SubAbility$ DBToken | SpellDescription$ Those creatures don't untap during their controller's next untap step.
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_eldrazi_scion_sac | TokenOwner$ You | LegacyImage$ c 1 1 eldrazi scion sac bfz | SpellDescription$ Create a 1/1 colorless Eldrazi Scion creature token. It has "Sacrifice this creature: Add {C}."
DeckHints:Type$Eldrazi
DeckHas:Ability$Mana.Colorless & Ability$Token

View File

@@ -2,7 +2,7 @@ Name:Ajani Vengeant
ManaCost:2 R W
Types:Legendary Planeswalker Ajani
Loyalty:3
A:AB$ CantUntapTurn | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Permanent | SpellDescription$ Target permanent doesn't untap during its controller's next untap step.
A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | KW$ HIDDEN This card doesn't untap during your next untap step. | ValidTgts$ Permanent | Permanent$ True | IsCurse$ True | SpellDescription$ Target permanent doesn't untap during its controller's next untap step.
A:AB$ DealDamage | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SubAbility$ DBGainLife | SpellDescription$ CARDNAME deals 3 damage to any target and you gain 3 life.
SVar:DBGainLife:DB$GainLife | LifeAmount$ 3
A:AB$ DestroyAll | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Player | TgtPrompt$ Select target player | ValidCards$ Land | SpellDescription$ Destroy all lands target player controls.

View File

@@ -3,7 +3,7 @@ ManaCost:7
Types:Artifact Creature Golem
PT:*/*
K:Trample
S:Mode$ Continuous | AffectedZone$ Battlefield | Affected$ Card.Self | CantUntap$ True | Description$ CARDNAME doesn't untap during your untap step.
K:CARDNAME doesn't untap during your untap step.
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of creatures on the battlefield.
SVar:X:Count$Valid Creature
A:AB$ Untap | Cost$ tapXType<5/Creature> | SpellDescription$ Untap CARDNAME.

View File

@@ -2,9 +2,10 @@ Name:Amber Prison
ManaCost:4
Types:Artifact
K:You may choose not to untap CARDNAME during your untap step.
A:AB$ Tap | Cost$ 4 T | ValidTgts$ Artifact,Creature,Land | TgtPrompt$ Select target artifact, creature, or land | SubAbility$ DBEffect | SpellDescription$ Tap target artifact, creature, or land. That permanent doesn't untap during its controller's untap step for as long as CARDNAME remains tapped. | StackDescription$ SpellDescription
SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | StaticAbilities$ DontUntap | Duration$ UntilUntaps | ForgetOnMoved$ Battlefield
SVar:DontUntap:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered | CantUntap$ True | Description$ Remembered doesn't untap during its controller's untap step for as long as EFFECTSOURCE remains tapped.
A:AB$ Tap | Cost$ 4 T | ValidTgts$ Artifact,Creature,Land | TgtPrompt$ Select target artifact, creature, or land | RememberTapped$ True | AlwaysRemember$ True | SpellDescription$ Tap target artifact, creature, or land. That permanent doesn't untap during its controller's untap step for as long as CARDNAME remains tapped. | StackDescription$ SpellDescription
S:Mode$ Continuous | Affected$ Card.IsRemembered | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step.
T:Mode$ Untaps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ ClearRemembered | Static$ True
SVar:ClearRemembered:DB$ Cleanup | ClearRemembered$ True
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/amber_prison.jpg
Oracle:You may choose not to untap Amber Prison during your untap step.\n{4}, {T}: Tap target artifact, creature, or land. That permanent doesn't untap during its controller's untap step for as long as Amber Prison remains tapped.

View File

@@ -3,7 +3,7 @@ ManaCost:2 R R
Types:Enchantment
K:ETBReplacement:Other:ChooseCT
SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentOppControls | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type.
S:Mode$ Continuous | Affected$ Creature.ChosenType | CantUntap$ True | Description$ Creatures of the chosen type don't untap during their controllers' untap steps.
S:Mode$ Continuous | Affected$ Creature.ChosenType | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Creatures of the chosen type don't untap during their controllers' untap steps.
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/an_zerrin_ruins.jpg
Oracle:As An-Zerrin Ruins enters the battlefield, choose a creature type.\nCreatures of the chosen type don't untap during their controllers' untap steps.

View File

@@ -3,7 +3,7 @@ ManaCost:U
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ U | ValidTgts$ Creature | AILogic$ Curse
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | CantUntap$ True | Description$ Enchanted creature doesn't untap during its controller's untap step. At the beginning of the upkeep of enchanted creature's controller, that player may discard a card at random. If the player does, untap that creature.
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature doesn't untap during its controller's untap step. At the beginning of the upkeep of enchanted creature's controller, that player may discard a card at random. If the player does, untap that creature.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ EnchantedController | TriggerZones$ Battlefield | OptionalDecider$ EnchantedController | Execute$ ApathyDiscard | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may discard a card at random. If the player does, untap that creature.
SVar:ApathyDiscard:DB$ Discard | Defined$ TriggeredPlayer | NumCards$ 1 | Mode$ Random | RememberDiscarded$ True | SubAbility$ ApathyUntap | References$ X
SVar:ApathyUntap:DB$ Untap | Defined$ Enchanted | SpellDescription$ Untap enchanted creature | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | References$ X | SubAbility$ DBCleanup

View File

@@ -2,7 +2,7 @@ Name:Apes of Rath
ManaCost:2 G G
Types:Creature Ape
PT:5/4
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ DBPump | TriggerDescription$ Whenever CARDNAME attacks, it doesn't untap during its controller's next untap step.
SVar:DBPump:DB$ CantUntapTurn | Defined$ Self
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ StayTapped | TriggerDescription$ Whenever CARDNAME attacks, it doesn't untap during its controller's next untap step.
SVar:StayTapped:DB$Pump | KW$ HIDDEN This card doesn't untap during your next untap step. | Defined$ Self | Permanent$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/apes_of_rath.jpg
Oracle:Whenever Apes of Rath attacks, it doesn't untap during its controller's next untap step.

View File

@@ -2,10 +2,7 @@ Name:Arbalest Elite
ManaCost:2 W W
Types:Creature Human Archer
PT:2/3
A:AB$ DealDamage | Cost$ 2 W T | ValidTgts$ Creature.attacking,Creature.blocking | TgtPrompt$ Select target attacking or blocking creature | NumDmg$ 3 | SubAbility$ DBEffect | SpellDescription$ CARDNAME deals 3 damage to target attacking or blocking creature. CARDNAME doesn't untap during your next untap step.
SVar:DBEffect:DB$ Effect | RememberObjects$ Self | StaticAbilities$ DontUntap | Duration$ Permanent | ForgetOnMoved$ Battlefield | Triggers$ RemoveEffect | SVars$ ExileEffect
SVar:DontUntap:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Card.IsRemembered | CantUntap$ True | Description$ EFFECTSOURCE don't untap during your next untap step.
SVar:RemoveEffect:Mode$ Phase | Phase$ Untap | ValidPlayer$ You | TriggerZones$ Command | Static$ True | Execute$ ExileEffect
SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
A:AB$ DealDamage | Cost$ 2 W T | ValidTgts$ Creature.attacking,Creature.blocking | TgtPrompt$ Select target attacking or blocking creature | NumDmg$ 3 | SubAbility$ DBStayTapped | SpellDescription$ CARDNAME deals 3 damage to target attacking or blocking creature. CARDNAME doesn't untap during your next untap step.
SVar:DBStayTapped:DB$Pump | KW$ HIDDEN This card doesn't untap during your next untap step. | Defined$ Self | Permanent$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/arbalest_elite.jpg
Oracle:{2}{W}, {T}: Arbalest Elite deals 3 damage to target attacking or blocking creature. Arbalest Elite doesn't untap during your next untap step.

View File

@@ -1,7 +1,7 @@
Name:Arena of the Ancients
ManaCost:3
Types:Artifact
S:Mode$ Continuous | Affected$ Creature.Legendary | CantUntap$ True | Description$ Legendary creatures don't untap during their controllers' untap steps.
S:Mode$ Continuous | Affected$ Creature.Legendary | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Legendary creatures don't untap during their controllers' untap steps.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTapAll | TriggerDescription$ When CARDNAME enters the battlefield, tap all legendary creatures.
SVar:TrigTapAll:DB$TapAll | ValidCards$ Creature.Legendary
SVar:NonStackingEffect:True

View File

@@ -1,7 +1,7 @@
Name:Back to Basics
ManaCost:2 U
Types:Enchantment
S:Mode$ Continuous | Affected$ Land.nonBasic | CantUntap$ True | Description$ Nonbasic lands don't untap during their controllers' untap steps.
S:Mode$ Continuous | Affected$ Land.nonBasic | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Nonbasic lands don't untap during their controllers' untap steps.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/back_to_basics.jpg

View File

@@ -1,7 +1,7 @@
Name:Barl's Cage
ManaCost:4
Types:Artifact
A:AB$ CantUntapTurn | Cost$ 3 | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Target creature doesn't untap during its controller's next untap step.
A:AB$ Pump | Cost$ 3 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN This card doesn't untap during your next untap step. | Permanent$ True | IsCurse$ True | SpellDescription$ Target creature doesn't untap during its controller's next untap step.
SVar:NonStackingEffect:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/barls_cage.jpg
Oracle:{3}: Target creature doesn't untap during its controller's next untap step.

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