...
 
Commits (74)
......@@ -68,6 +68,12 @@ Discord channel [here](https://discordapp.com/channels/267367946135928833/267742
TBD
# Card Scripting
Visit [this page](https://www.slightlymagic.net/wiki/Forge_API) for information on scripting.
Card scripting resources are found in the forge-gui/res/ path.
# General Notes
## Project Hierarchy
......@@ -95,6 +101,8 @@ The platform-specific projects are:
### forge-gui
The forge-gui project includes the scripting resource definitions in the res/ path.
### forge-gui-android
Libgdx-based backend targeting Android. Requires Android SDK and relies on forge-gui-mobile for GUI logic.
......
......@@ -2394,6 +2394,8 @@ public class ComputerUtilCombat {
// Predict replacement effects
for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) {
for (final ReplacementEffect re : ca.getReplacementEffects()) {
if (re == null)
continue;
Map<String, String> params = re.getMapParams();
if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) {
continue;
......
......@@ -1335,6 +1335,8 @@ public class ComputerUtilMana {
for (final Player p : game.getPlayers()) {
for (final Card crd : p.getAllCards()) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
if (replacementEffect == null)
continue;
if (replacementEffect.requirementsCheck(game)
&& replacementEffect.getMapParams().containsKey("ManaReplacement")
&& replacementEffect.zonesCheck(game.getZoneOf(crd))) {
......
......@@ -232,8 +232,11 @@ public class AttachAi extends SpellAbilityAi {
&& game.getPhaseHandler().getNextTurn().equals(ai) && MyRandom.percentTrue(chanceToCastAtEOT);
boolean alternativeConsiderations = hasFloatMana || willDiscardNow || willDieNow || willRespondToStack || willCastAtEOT || willCastEarly;
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
try {
if (!alternativeConsiderations && (combat == null || game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) || (!combat.isAttacking(attachTarget) && !combat.isBlocking(attachTarget))) {
return false;
}
} catch (Exception e){
return false;
}
......
......@@ -311,13 +311,20 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
if (sa.hasParam("Adapt") && source.getCounters(CounterType.P1P1) > 0) {
return false;
}
// TODO handle proper calculation of X values based on Cost
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
if (sa.hasParam("Adapt")) {
Game game = ai.getGame();
Combat combat = game.getCombat();
if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
return false;
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return doCombatAdaptLogic(source, amount, combat);
}
}
if ("Fight".equals(logic)) {
int nPump = 0;
if (type.equals("P1P1")) {
......@@ -1049,4 +1056,39 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
private boolean doCombatAdaptLogic(Card source, int amount, Combat combat) {
if (combat.isAttacking(source)) {
if (!combat.isBlocked(source)) {
return true;
} else {
for (Card blockedBy : combat.getBlockers(source)) {
if (blockedBy.getNetToughness() > source.getNetPower()
&& blockedBy.getNetToughness() <= source.getNetPower() + amount) {
return true;
}
}
int totBlkPower = Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
if (source.getNetToughness() <= totBlkPower
&& source.getNetToughness() + amount > totBlkPower) {
return true;
}
}
} else if (combat.isBlocking(source)) {
for (Card blocked : combat.getAttackersBlockedBy(source)) {
if (blocked.getNetToughness() > source.getNetPower()
&& blocked.getNetToughness() <= source.getNetPower() + amount) {
return true;
}
}
int totAtkPower = Aggregates.sum(combat.getAttackersBlockedBy(source), CardPredicates.Accessors.fnGetNetPower);
if (source.getNetToughness() <= totAtkPower
&& source.getNetToughness() + amount > totAtkPower) {
return true;
}
}
return false;
}
}
......@@ -17,91 +17,96 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
if (!ai.canGainLife()) {
return false;
}
try {
// Ability_Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
final int myLife = ai.getLife();
final Player opponent = ComputerUtil.getOpponentFor(ai);
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
if (!ai.canGainLife()) {
return false;
}
// Don't use setLife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// Don't use setLife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
return false;
}
// TODO add AI logic for that
if (sa.hasParam("Redistribute")) {
return false;
}
// TODO add AI logic for that
if (sa.hasParam("Redistribute")) {
return false;
}
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// TODO handle proper calculation of X values based on Cost and what
// would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
source.setSVar("PayX", Integer.toString(xPay));
amount = xPay;
} else {
amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
}
// prevent run-away activations - first time will always return true
final boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
// prevent run-away activations - first time will always return true
final boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
} else {
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
// if we can only target the human, and the Human's life
// would
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
} else {
return false;
if ((amount > myLife) && (myLife <= 10)) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
} else if (amount > myLife) {
sa.getTargets().add(ai);
} else {
return false;
}
}
}
} else {
if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) {
return false;
} else if (myLife > amount) { // will decrease computer's
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
} else {
if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) {
return false;
} else if (myLife > amount) { // will decrease computer's
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false;
}
}
}
if (amount <= myLife) {
return false;
}
}
if (amount <= myLife) {
return false;
}
}
// if life is in danger, always activate
if ((myLife < 3) && (amount > myLife)) {
return true;
}
// if life is in danger, always activate
if ((myLife < 3) && (amount > myLife)) {
return true;
}
return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
} catch (Exception e) {
return false;
}
}
@Override
......
......@@ -37,7 +37,8 @@ public class Localizer {
MessageFormat formatter = null;
try {
formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
//formatter = new MessageFormat(resourceBundle.getString(key.toLowerCase()), locale);
formatter = new MessageFormat(resourceBundle.getString(key.toString()), locale);
} catch (final IllegalArgumentException | MissingResourceException e) {
e.printStackTrace();
}
......
......@@ -126,7 +126,7 @@ public class GameView extends TrackableObject {
void updateGameOver(final Game game) {
set(TrackableProperty.GameOver, game.isGameOver());
set(TrackableProperty.MatchOver, game.getMatch().isMatchOver());
<<<<<<< HEAD
//<<<<<<< HEAD
if (game.getOutcome() == null || game.getOutcome().getWinningLobbyPlayer() == null) {
set(TrackableProperty.WinningPlayerName, -1);
set(TrackableProperty.WinningTeam, -1);
......@@ -134,12 +134,12 @@ public class GameView extends TrackableObject {
set(TrackableProperty.WinningPlayerName, game.getOutcome().getWinningLobbyPlayer().getName());
set(TrackableProperty.WinningTeam, game.getOutcome().getWinningTeam());
}
=======
//=======
if (game.getOutcome() != null && game.getOutcome().getWinningLobbyPlayer() != null) {
set(TrackableProperty.WinningPlayerName, game.getOutcome().getWinningLobbyPlayer().getName());
}
set(TrackableProperty.WinningTeam, game.getOutcome() == null ? -1 : game.getOutcome().getWinningTeam());
>>>>>>> cdb47e0e10fc7ebee7e65769ad027f9335cfecef
//>>>>>>> cdb47e0e10fc7ebee7e65769ad027f9335cfecef
}
public GameLog getGameLog() {
......
......@@ -166,7 +166,8 @@ public class EffectEffect extends SpellAbilityEffect {
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, eff, true);
final ReplacementEffect addedReplacement = eff.addReplacementEffect(parsedReplacement);
addedReplacement.setIntrinsic(true);
if (addedReplacement != null)
addedReplacement.setIntrinsic(true);
}
}
......
......@@ -1810,13 +1810,17 @@ public class Card extends GameEntity implements Comparable<Card> {
// here. The rest will be printed later.
StringBuilder replacementEffects = new StringBuilder();
for (final ReplacementEffect replacementEffect : state.getReplacementEffects()) {
if (!replacementEffect.isSecondary()) {
String text = replacementEffect.toString();
if (text.equals("CARDNAME enters the battlefield tapped.")) {
sb.append(text).append("\r\n");
} else {
replacementEffects.append(text).append("\r\n");
try {
if (!replacementEffect.isSecondary()) {
String text = replacementEffect.toString();
if (text.equals("CARDNAME enters the battlefield tapped.")) {
sb.append(text).append("\r\n");
} else {
replacementEffects.append(text).append("\r\n");
}
}
} catch (Exception e){
}
}
......
......@@ -433,9 +433,9 @@ public class CardFactory {
for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true));
<<<<<<< HEAD
//<<<<<<< HEAD
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
=======
//=======
// Name first so Senty has the Card name
c.setName(face.getName());
......@@ -444,19 +444,19 @@ public class CardFactory {
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true));
for (Entry<String, String> v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
>>>>>>> cdb47e0e10fc7ebee7e65769ad027f9335cfecef
//>>>>>>> cdb47e0e10fc7ebee7e65769ad027f9335cfecef
// keywords not before variables
c.addIntrinsicKeywords(face.getKeywords(), false);
<<<<<<< HEAD
//<<<<<<< HEAD
c.setName(face.getName());
c.setManaCost(face.getManaCost());
c.setText(face.getNonAbilityText());
=======
//=======
c.setManaCost(face.getManaCost());
c.setText(face.getNonAbilityText());
>>>>>>> cdb47e0e10fc7ebee7e65769ad027f9335cfecef
//>>>>>>> cdb47e0e10fc7ebee7e65769ad027f9335cfecef
c.getCurrentState().setBaseLoyalty(face.getInitialLoyalty());
......
......@@ -1236,10 +1236,10 @@ public class CardFactoryUtil {
return doXMath(c.getPseudoKickerMagnitude(), m, c);
}
// Count$IfMainPhase.<numMain>.<numNotMain> // 7/10
if (sq[0].contains("IfMainPhase")) {
// Count$IfCastInOwnMainPhase.<numMain>.<numNotMain> // 7/10
if (sq[0].contains("IfCastInOwnMainPhase")) {
final PhaseHandler cPhase = cc.getGame().getPhaseHandler();
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc);
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.getPlayerTurn().equals(cc) && c.getCastFrom() != null;
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), m, c);
}
......
......@@ -967,6 +967,19 @@ public class CardProperty {
if (restriction.startsWith("Remembered") || restriction.startsWith("Imprinted")) {
CardCollection list = AbilityUtils.getDefinedCards(source, restriction, spellAbility);
return CardLists.filter(list, CardPredicates.sharesNameWith(card)).isEmpty();
} else if (restriction.equals("YourGraveyard")) {
return CardLists.filter(sourceController.getCardsIn(ZoneType.Graveyard), CardPredicates.sharesNameWith(card)).isEmpty();
} else if (restriction.equals("OtherYourBattlefield")) {
// Obviously it's going to share a name with itself, so consider that in the
CardCollection list = CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), CardPredicates.sharesNameWith(card));
if (list.size() == 1) {
Card c = list.getFirst();
if (c.getTimestamp() == card.getTimestamp() && c.getId() == card.getId()) {
list.remove(card);
}
}
return list.isEmpty();
}
}
} else if (property.startsWith("sharesControllerWith")) {
......
......@@ -91,15 +91,23 @@ public class ReplacementHandler {
// Use "CheckLKIZone" parameter to test for effects that care abut where the card was last (e.g. Kalitas, Traitor of Ghet
// getting hit by mass removal should still produce tokens).
Zone cardZone = "True".equals(replacementEffect.getMapParams().get("CheckSelfLKIZone")) ? game.getChangeZoneLKIInfo(crd).getLastKnownZone() : game.getZoneOf(crd);
Zone cardZone = null;
try {
cardZone = "True".equals(replacementEffect.getMapParams().get("CheckSelfLKIZone")) ? game.getChangeZoneLKIInfo(crd).getLastKnownZone() : game.getZoneOf(crd);
} catch (Exception e){
cardZone = null;
}
// Replacement effects that are tied to keywords (e.g. damage prevention effects - if the keyword is removed, the replacement
// effect should be inactive)
if (replacementEffect.hasParam("TiedToKeyword")) {
String kw = replacementEffect.getParam("TiedToKeyword");
if (!crd.hasKeyword(kw)) {
continue;
try {
if (replacementEffect.hasParam("TiedToKeyword")) {
String kw = replacementEffect.getParam("TiedToKeyword");
if (!crd.hasKeyword(kw)) {
continue;
}
}
} catch (Exception e){
continue;
}
if (!replacementEffect.hasRun()
......@@ -295,6 +303,8 @@ public class ReplacementHandler {
* @return The finished instance
*/
private static ReplacementEffect parseReplacement(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
if (mapParams.get("Event") == null)
return null;
final ReplacementType rt = ReplacementType.smartValueOf(mapParams.get("Event"));
ReplacementEffect ret = rt.createReplacement(mapParams, host, intrinsic);
......@@ -312,7 +322,7 @@ public class ReplacementHandler {
public boolean visit(Card c) {
List<ReplacementEffect> toRemove = Lists.newArrayList();
for (ReplacementEffect rep : c.getReplacementEffects()) {
if (rep.isTemporary()) {
if (rep != null && rep.isTemporary()) {
toRemove.add(rep);
}
}
......@@ -326,7 +336,11 @@ public class ReplacementHandler {
@Override
public boolean visit(Card c) {
for (int i = 0; i < c.getReplacementEffects().size(); i++) {
c.getReplacementEffects().get(i).setTemporarilySuppressed(false);
try {
c.getReplacementEffects().get(i).setTemporarilySuppressed(false);
} catch (Exception e){
}
}
return true;
}
......
package forge.screens.home;
/**
/**
* Submenus each belong to a menu group, which
* is used for several functions, such as expanding
* and collapsing in the menu.
......
......@@ -114,6 +114,8 @@ public class GridCard {
return false;
if (getCard().getName().equals("Rat Colony"))
return false;
if (getCard().getName().equals("Persistent Petitioners"))
return false;
return true;
}
......
......@@ -85,7 +85,10 @@ public enum GridFormat {
if (this.ordinal() > 0 && ShivaUtility.rand.nextInt(100)<60){
return nextDown().matrixSet();
}
return setList.get(ShivaUtility.rand.nextInt(setList.size()));
GridSet gs = setList.get(ShivaUtility.rand.nextInt(setList.size()));
if (!gs.getIsActualSet())
return null;
return gs;
}
public PaperCard getColourCard(RenColour rc){
......
......@@ -133,6 +133,12 @@ public class GridSet {
setProperties.setProperty("BanList",s);
doSave = true;
}
public boolean getIsActualSet(){
String s = setProperties.getProperty("actualSet","True");
boolean b = Boolean.parseBoolean(s);
return b;
}
public void addBanList(String val){
setBanList(GridUtilities.addStringtoArray(getBanList(),val, false));
}
......@@ -477,7 +483,7 @@ public class GridSet {
PaperCard pc2 = list.get(rand.nextInt(list.size()));
if (pc2.getRarity().ordinal() > pc.getRarity().ordinal())
pc = pc2;
if (pc != null && !pc.getRules().getType().isLand() && !loopBlock ){
if (pc != null && !pc.getRules().getType().isLand() && !loopBlock && rand.nextInt(100) < 60 ){
loopBlock = true;
pc = format.returnHighest(new PaperCard[]{pc, getColourTypeCard(rc, rgt), getColourTypeCard(rc, rgt)});
loopBlock = false;
......@@ -636,7 +642,7 @@ public class GridSet {
if (!archetypes.containsKey("Rare") && canCreateNewArch())
getArchetype(rt, "Rare");
if (!archetypes.containsKey("Mythic") && canCreateNewArch())
if (!archetypes.containsKey("Mythic") && canCreateNewArch())`
getArchetype(rt, "Mythic");
}
}
......@@ -806,6 +812,10 @@ public class GridSet {
public Integer[] colourCounts;
public void initCardList(){
if (!getIsActualSet()){
allowedColours = RenColour.Rainbow;
return;
}
cardList.clear();
cardListCards.clear();
......@@ -853,6 +863,10 @@ public class GridSet {
}
public void initSet(String code){
if (!getIsActualSet()){
allowedColours = RenColour.Rainbow;
return;
}
if (code.equals("") || code.equals(" ")) return;
System.out.println("InitializingSet: "+code);
......
......@@ -390,7 +390,7 @@ public class GridTourney implements Runnable {
String elo = ((int) Math.round(t.getElo())) + "";
while (elo.length() < 4)
elo += " ";
GridUtilities.Log("TribeList", String.format("%s\t%f\t%s\t%d\tTier:%d", t.getMarginName(), t.adjustedNumTourneys(0), elo, t.getRunCount(), t.tier()));
GridUtilities.Log("TribeList", String.format("%s\t%f\t%s\t%d\tTier:%d\t%f", t.getMarginName(), t.adjustedNumTourneys(0), elo, t.getRunCount(), t.tier(), t.getBestTrueElo()));
}
if (possibleCompetitors.get(0).getRunCount() % 10 == 0)
main = possibleCompetitors.get(0);
......@@ -510,19 +510,23 @@ public class GridTourney implements Runnable {
main.compareTribe(foe);
foe.compareTribe(main);
if (forceArch == null && ((doBestDeck && foe.getElo() < main.getElo()) ||
(GridRound.roundRuns == 1 && main.bredOne && !foe.bredOne))) {
forceHigh = false;
SaveAll();
start = System.currentTimeMillis();
getFoe(foe);
} else if (!main.bredOne) {
SaveAll();
start = System.currentTimeMillis();
getFoe(main);
gameCount ++;
if (gameCount < 10) {
if (forceArch == null && ((doBestDeck && foe.getElo() < main.getElo()) ||
(GridRound.roundRuns == 1 && main.bredOne && !foe.bredOne))) {
forceHigh = false;
SaveAll();
start = System.currentTimeMillis();
getFoe(foe);
} else if (!main.bredOne) {
SaveAll();
start = System.currentTimeMillis();
getFoe(main);
}
}
}
public int gameCount = 0;
private boolean quittin = false;
public void tokenAndQuit(String mess, int id){
......
......@@ -156,6 +156,17 @@ public class GridTribe {
setSave();
}
public void setBestTrueElo(GridDeck deck){
tribeProperties.setProperty("BestTrueElo",deck.getElo()+"");
setSave();
}
public double getBestTrueElo(){
String s = tribeProperties.getProperty("BestTrueElo","0");
double i = Double.parseDouble(s);
return Math.max(i,getBestElo())/2;
}
public void addElo(Double amt){
amt += getElo();
setElo(amt);
......@@ -339,6 +350,10 @@ public class GridTribe {
// skip = true;
}
//*/
if (getBestTrueElo() >= getRunCount() || getRunCount() <= 10)
skip = false;
if (!GridTourney.loadPreBase && archetype.set.format.ordinal() > GridTourney.highestBase.ordinal())
skip = true;
/*
......@@ -354,6 +369,7 @@ public class GridTribe {
return;
}
*/
if (skip){
//if (getRunCount() == 0)
//System.out.println("\tSkip: "+getViewName());
......@@ -783,10 +799,15 @@ public class GridTribe {
private void calcBest(){
ArrayList<GridDeck> algd = bestDecks();
algd.sort(GridDeck.eloComparitor);
if (algd.size() == 0 || algd.get(0) == null)
return;
setBest(algd.get(0));
algd.sort(GridDeck.eloComparitor);
setBest(algd.get(0)); // sets the recorded best deck
algd.clear();
for (GridDeck gd : decks)
algd.add(gd);
algd.sort(GridDeck.eloComparitor);
setBestTrueElo(algd.get(0)); // sets the recorded best elo
}
public void compareTribe(GridTribe tribe){
......
......@@ -235,6 +235,10 @@ public class GridUtilities {
public static boolean checkTheme (PaperCard pc, String theme){
if (textSearch(pc,"non-"+theme) && !textSearch(pc, " "+theme))
return false;
if (theme.equals("Multicolor")){
if (pc.getRules().getColor().isMulticolor())
return true;
}
if (theme.equals("Freebies")){
if (textSearch(pc, "onto the battlefield") && textSearch(pc, "from")){
return true;
......
......@@ -9,17 +9,17 @@ public enum MTGFaction {
Gatewatch(MTGFactionType.Heroic, RenColour.Altruism, new String[]{}),
Bolas(MTGFactionType.Dragonlord, RenColour.Grixis, new String[]{}),
Azorius(MTGFactionType.Guild, RenColour.Azorius, new String[]{"Forecast","Detain","Azor"}),
Azorius(MTGFactionType.Guild, RenColour.Azorius, new String[]{"Forecast","Detain","Azor","Addendum"}),
Dimir(MTGFactionType.Guild, RenColour.Dimir, new String[]{"Transmute","Cipher","Surveil"}),
Rakdos(MTGFactionType.Guild, RenColour.Rakdos, new String[]{"Hellbent","Unleash"}),
Gruul(MTGFactionType.Guild, RenColour.Gruul, new String[]{"Bloodthirst","Bloodrush"}),
Rakdos(MTGFactionType.Guild, RenColour.Rakdos, new String[]{"Hellbent","Unleash","Spectacle"}),
Gruul(MTGFactionType.Guild, RenColour.Gruul, new String[]{"Bloodthirst","Bloodrush","Riot"}),
Selesnya(MTGFactionType.Guild, RenColour.Selesnya, new String[]{"Convoke","Proliferate"}),
Orzhov(MTGFactionType.Guild, RenColour.Orzhov, new String[]{"Haunt","Extort","Drain"}),
Orzhov(MTGFactionType.Guild, RenColour.Orzhov, new String[]{"Haunt","Extort","Drain","Afterlife"}),
Izzet(MTGFactionType.Guild, RenColour.Izzet, new String[]{"Replicate","Overload","Jump-start"}),
Golgari(MTGFactionType.Guild, RenColour.Golgari, new String[]{"Dredge","Scavenge","Undergrowth"}),
Boros(MTGFactionType.Guild, RenColour.Boros, new String[]{"Radiance","Battlefront","Mentor"}),
Simic(MTGFactionType.Guild, RenColour.Simic, new String[]{"Graft","Evolve"}),
Simic(MTGFactionType.Guild, RenColour.Simic, new String[]{"Graft","Evolve","Adapt","PlusPlus"}),
Jeskai(MTGFactionType.Khan, RenColour.Jeskai, new String[]{"Prowess","Rebound","Narset"}),
Sultai(MTGFactionType.Khan, RenColour.Sultai, new String[]{"Delve","Exploit","Sidisi","Kheru"}),
......
......@@ -192,6 +192,22 @@ public enum RenColour {
return Rainbow;
}
public static boolean isHybrid(PaperCard pc){
RenColour found = Colourless;
for (RenColour rc : RenColour.values){
if (rc == Colourless)
continue;
if (rc.canPlay(pc)){
if (found == Colourless || !rc.contains(found)) {
if (found != Colourless)
return true;
found = rc;
}
}
}
return false;
}
public boolean canPlay(PaperCard pc) {
if (pc == null) return false;
if (pc.getRules().getColorIdentity().isColorless()) return true;
......
......@@ -18,6 +18,7 @@ import forge.toolbox.FComboBox;
import forge.toolbox.FComboBoxPanel;
import forge.toolbox.FLabel;
import forge.toolbox.FOptionPane;
import forge.util.Localizer;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
......@@ -38,6 +39,8 @@ import java.util.List;
public enum CSubmenuPreferences implements ICDoc {
/** */
SINGLETON_INSTANCE;
final Localizer localizer = Localizer.getInstance();
private VSubmenuPreferences view;
private ForgePreferences prefs;
......@@ -66,7 +69,7 @@ public enum CSubmenuPreferences implements ICDoc {
if (updating) { return; }
// prevent changing DEV_MODE while network game running
if (FServerManager.getInstance().isMatchActive()) {
System.out.println("Can't change DEV_MODE while a network match is in progress!");
System.out.println(localizer.getMessage("CantChangeDevModeWhileNetworkMath"));
return;
}
......@@ -192,7 +195,7 @@ public enum CSubmenuPreferences implements ICDoc {
public void run() {
prefs.setPref(FPref.DISABLE_DISPLAY_JAVA_8_UPDATE_WARNING, false);
prefs.save();
FOptionPane.showMessageDialog("Compatibility warnings re-enabled!");
FOptionPane.showMessageDialog(localizer.getMessage("CompatibilityWarningsReEnabled"));
}
});
......@@ -242,10 +245,8 @@ public enum CSubmenuPreferences implements ICDoc {
}
private void resetForgeSettingsToDefault() {
final String userPrompt =
"This will reset all preferences to their defaults and restart Forge.\n\n" +
"Reset and restart Forge?";
if (FOptionPane.showConfirmDialog(userPrompt, "Reset Settings")) {
final String userPrompt =localizer.getMessage("AresetForgeSettingsToDefault");
if (FOptionPane.showConfirmDialog(userPrompt, localizer.getMessage("TresetForgeSettingsToDefault"))) {
final ForgePreferences prefs = FModel.getPreferences();
prefs.reset();
prefs.save();
......@@ -255,38 +256,28 @@ public enum CSubmenuPreferences implements ICDoc {
}
private void resetDeckEditorLayout() {
final String userPrompt =
"This will reset the Deck Editor screen layout.\n" +
"All tabbed views will be restored to their default positions.\n\n" +
"Reset layout?";
if (FOptionPane.showConfirmDialog(userPrompt, "Reset Deck Editor Layout")) {
final String userPrompt =localizer.getMessage("AresetDeckEditorLayout");
if (FOptionPane.showConfirmDialog(userPrompt, localizer.getMessage("TresetDeckEditorLayout"))) {
if (FScreen.DECK_EDITOR_CONSTRUCTED.deleteLayoutFile()) {
FOptionPane.showMessageDialog("Deck Editor layout has been reset.");
FOptionPane.showMessageDialog(localizer.getMessage("OKresetDeckEditorLayout"));
}
}
}
private void resetWorkshopLayout() {
final String userPrompt =
"This will reset the Workshop screen layout.\n" +
"All tabbed views will be restored to their default positions.\n\n" +
"Reset layout?";
if (FOptionPane.showConfirmDialog(userPrompt, "Reset Workshop Layout")) {
final String userPrompt =localizer.getMessage("AresetWorkshopLayout");
if (FOptionPane.showConfirmDialog(userPrompt, localizer.getMessage("TresetWorkshopLayout"))) {
if (FScreen.WORKSHOP_SCREEN.deleteLayoutFile()) {
FOptionPane.showMessageDialog("Workshop layout has been reset.");
FOptionPane.showMessageDialog(localizer.getMessage("OKresetWorkshopLayout"));
}
}
}
private void resetMatchScreenLayout() {
final String userPrompt =
"This will reset the layout of the Match screen.\n" +
"If you want to save the current layout first, please use " +
"the Dock tab -> Save Layout option in the Match screen.\n\n" +
"Reset layout?";
if (FOptionPane.showConfirmDialog(userPrompt, "Reset Match Screen Layout")) {
final String userPrompt =localizer.getMessage("AresetMatchScreenLayout");
if (FOptionPane.showConfirmDialog(userPrompt, localizer.getMessage("TresetMatchScreenLayout"))) {
if (FScreen.deleteMatchLayoutFile()) {
FOptionPane.showMessageDialog("Match Screen layout has been reset.");
FOptionPane.showMessageDialog(localizer.getMessage("OKresetMatchScreenLayout"));
}
}
}
......
......@@ -31,6 +31,7 @@ import forge.toolbox.FSkin.SkinColor;
import forge.toolbox.FSkin.SkinFont;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.special.CardZoomer;
import forge.util.Localizer;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
......@@ -54,12 +55,13 @@ public enum VSubmenuAchievements implements IVSubmenu<CSubmenuAchievements> {
private static final SkinColor NOT_EARNED_COLOR = TEXT_COLOR.alphaColor(128);
private static final SkinColor TEXTURE_OVERLAY_COLOR = FSkin.getColor(Colors.CLR_THEME);
private static final SkinColor BORDER_COLOR = FSkin.getColor(Colors.CLR_BORDERS);
final Localizer localizer = Localizer.getInstance();
// Fields used with interface IVDoc
private DragCell parentCell;
private final DragTab tab = new DragTab("Achievements");
private final DragTab tab = new DragTab(localizer.getMessage("Achievements"));
private final FLabel lblTitle = new FLabel.Builder()
.text("Achievements").fontAlign(SwingConstants.CENTER)
.text(localizer.getMessage("Achievements")).fontAlign(SwingConstants.CENTER)
.opaque(true).fontSize(16).build();
private final FComboBox<AchievementCollection> cbCollections = new FComboBox<AchievementCollection>();
private final TrophyCase trophyCase = new TrophyCase();
......@@ -167,7 +169,7 @@ public enum VSubmenuAchievements implements IVSubmenu<CSubmenuAchievements> {
*/
@Override
public String getMenuTitle() {
return "Achievements";
return localizer.getMessage("Achievements");
}
/* (non-Javadoc)
......
......@@ -12,6 +12,7 @@ import forge.screens.home.IVSubmenu;
import forge.screens.home.VHomeUI;
import forge.toolbox.*;
import forge.util.FileUtil;
import forge.util.Localizer;
import forge.util.RuntimeVersion;
import net.miginfocom.swing.MigLayout;
......@@ -29,6 +30,8 @@ import java.awt.event.ActionListener;
public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
/** */
SINGLETON_INSTANCE;
final Localizer localizer = Localizer.getInstance();
// Fields used with interface IVDoc
private DragCell parentCell;
......@@ -38,15 +41,15 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
private final JPanel pnlContent = new JPanel(new MigLayout("insets 0, gap 0, wrap, ay center"));
private final FScrollPane scrContent = new FScrollPane(pnlContent, false);
private final FLabel btnDownloadSetPics = _makeButton("Download LQ Set Pictures");
private final FLabel btnDownloadPics = _makeButton("Download LQ Card Pictures");
private final FLabel btnDownloadQuestImages = _makeButton("Download Quest Images");
private final FLabel btnDownloadAchievementImages = _makeButton("Download Achievement Images");
private final FLabel btnReportBug = _makeButton("Report a Bug");
private final FLabel btnImportPictures = _makeButton("Import Data");
private final FLabel btnHowToPlay = _makeButton("How To Play");
private final FLabel btnDownloadPrices = _makeButton("Download Card Prices");
private final FLabel btnLicensing = _makeButton("License Details");
private final FLabel btnDownloadSetPics = _makeButton(localizer.getMessage("btnDownloadSetPics"));
private final FLabel btnDownloadPics = _makeButton(localizer.getMessage("btnDownloadPics"));
private final FLabel btnDownloadQuestImages = _makeButton(localizer.getMessage("btnDownloadQuestImages"));
private final FLabel btnDownloadAchievementImages = _makeButton(localizer.getMessage("btnDownloadAchievementImages"));
private final FLabel btnReportBug = _makeButton(localizer.getMessage("btnReportBug"));
private final FLabel btnImportPictures = _makeButton(localizer.getMessage("btnImportPictures"));
private final FLabel btnHowToPlay = _makeButton(localizer.getMessage("btnHowToPlay"));
private final FLabel btnDownloadPrices = _makeButton(localizer.getMessage("btnDownloadPrices"));
private final FLabel btnLicensing = _makeButton(localizer.getMessage("btnLicensing"));
/**
* Constructor.
......@@ -61,47 +64,48 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
if (javaRecentEnough()) {
pnlContent.add(btnDownloadPics, constraintsBTN);
pnlContent.add(_makeLabel("Download default card picture for each card."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPics")), constraintsLBL);
pnlContent.add(btnDownloadSetPics, constraintsBTN);
pnlContent.add(_makeLabel("Download all pictures of each card (one for each set the card appeared in)"), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadSetPics")), constraintsLBL);
pnlContent.add(btnDownloadQuestImages, constraintsBTN);
pnlContent.add(_makeLabel("Download tokens and icons used in Quest mode."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadQuestImages")), constraintsLBL);
pnlContent.add(btnDownloadAchievementImages, constraintsBTN);
pnlContent.add(_makeLabel("Download achievement images to really make your trophies stand out."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadAchievementImages")), constraintsLBL);
pnlContent.add(btnDownloadPrices, constraintsBTN);
pnlContent.add(_makeLabel("Download up-to-date price list for in-game card shops."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadPrices")), constraintsLBL);
} else {
String text = "Your version of Java is too old to use the content downloaders.";
String text = localizer.getMessage("lblYourVersionOfJavaIsTooOld");
FLabel label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build();
pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 30px 3px");
text = "Please update to the latest version of Java 8 to use this feature.";
text = localizer.getMessage("lblPleaseUpdateToTheLatestVersionOfJava");
label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build();
pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 0 36px");
text = "You're running " + System.getProperty("java.version") + ". You need at least version 1.8.0_101.";
text = localizer.getMessage("lblYoureRunning") + " " + System.getProperty("java.version");
text = text + " . " + localizer.getMessage("lblYouNeedAtLeastJavaVersion") ;
label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build();
pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 0 36px");
}
pnlContent.add(btnImportPictures, constraintsBTN);
pnlContent.add(_makeLabel("Import data from a local directory."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblImportPictures")), constraintsLBL);
pnlContent.add(btnReportBug, constraintsBTN);
pnlContent.add(_makeLabel("Something broken?"), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblReportBug")), constraintsLBL);
pnlContent.add(btnHowToPlay, constraintsBTN);
pnlContent.add(_makeLabel("Rules of the Game."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblHowToPlay")), constraintsLBL);
pnlContent.add(btnLicensing, constraintsBTN);
pnlContent.add(_makeLabel("Forge legal."), constraintsLBL);
pnlContent.add(_makeLabel(localizer.getMessage("lblLicensing")), constraintsLBL);
}
......@@ -210,7 +214,7 @@ public enum VSubmenuDownloaders implements IVSubmenu<CSubmenuDownloaders> {
*/
@Override
public String getMenuTitle() {
return "Content Downloaders";
return localizer.getMessage("ContentDownloaders");
}
/* (non-Javadoc)
......
......@@ -28,7 +28,7 @@ import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinnedTextArea;
import net.miginfocom.swing.MigLayout;
import forge.util.Localizer;
import javax.swing.*;
/**
......@@ -40,10 +40,11 @@ import javax.swing.*;
public enum VSubmenuReleaseNotes implements IVSubmenu<CSubmenuReleaseNotes> {
SINGLETON_INSTANCE;
final Localizer localizer = Localizer.getInstance();
// Fields used with interface IVDoc
private DragCell parentCell;
private final DragTab tab = new DragTab("Release Notes");
private final DragTab tab = new DragTab(localizer.getMessage("ReleaseNotes"));
private final JPanel pnlMain = new JPanel();
private SkinnedTextArea tar;
......@@ -93,7 +94,7 @@ public enum VSubmenuReleaseNotes implements IVSubmenu<CSubmenuReleaseNotes> {
*/
@Override
public String getMenuTitle() {
return "Release Notes";
return localizer.getMessage("ReleaseNotes");
}
/* (non-Javadoc)
......
......@@ -1528,7 +1528,13 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
modelPath = "";
setSaved(true);
}
editor.setDeck(model.getHumanDeck());
if (model != null) {
editor.setDeck(model.getHumanDeck());
}
else {
editor.setDeck(null);
}
}
private boolean isModelInSyncWithFolder() {
......
......@@ -4,7 +4,7 @@ Types:Creature Elemental
PT:1/1
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDig | TriggerDescription$ At the beginning of your upkeep, reveal cards from the top of your library until you reveal a creature card. Until your next turn, CARDNAME's base power becomes twice that card's power and its toughness. Put the revealed cards on the bottom of your library in a random order.
SVar:TrigDig:DB$ DigUntil | Reveal$ True | Valid$ Creature | ValidDescription$ creature card | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | ImprintRevealed$ True | SubAbility$ DBAnimate
SVar:DBAnimate:DB$ Animate | Power$ X | Toughness$ Y | SubAbility$ DBMovetoLib
SVar:DBAnimate:DB$ Animate | Power$ X | Toughness$ Y | UntilYourNextTurn$ True | SubAbility$ DBMovetoLib
SVar:DBMovetoLib:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered,Card.IsImprinted | Origin$ Exile | Destination$ Library | RandomOrder$ True | LibraryPosition$ -1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
SVar:X:Remembered$CardPower/Times.2
......
......@@ -5,8 +5,8 @@ PT:5/4
K:Flash
K:Flying
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEffect | TriggerDescription$ When CARDNAME enters the battlefield, until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.
SVar:TrigEffect:DB$ Effect | Name$ CARDNAME Effect | ReplacementEffects$ ElderscaleCondition | Duration$ UntilEndOfTurn
SVar:SelflessDamage:Event$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ DamageLifeThreshold:1 | Description$ Spells can't reduce your life total to less than 1 reduces it to 1 instead.
SVar:TrigEffect:DB$ Effect | Name$ Angel of Grace Effect | StaticAbilities$ SelflessDamage | Description$ Until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.
SVar:SelflessDamage:Mode$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ DamageLifeThreshold:1 | Description$ Until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.
A:AB$ SetLife | Cost$ 4 W W ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard | LifeAmount$ 10 | SpellDescription$ Your life total becomes 10.
SVar:RemRandomDeck:True
Oracle:Flash\nFlying\nWhen Angel of Grace enters the battlefield, until end of turn, damage that would reduce your life total to less than 1 reduces it to 1 instead.\n{4}{W}{W}, Exile Angel of Grace from your graveyard: Your life total becomes 10.
Name:Angelic Reward
ManaCost:3 W W
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ 3 W W | ValidTgts$ Creature | AILogic$ Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddToughness$ 3 | AddKeyword$ Flying | Description$ Enchanted creature gets +3/+3 and has flying.
Oracle:Enchant creature\nEnchanted creature gets +3/+3 and has flying.
......@@ -2,5 +2,5 @@ Name:Arrester's Admonition
ManaCost:2 U
Types:Instant
A:SP$ ChangeZone | Cost$ 2 U | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBAddendum | SpellDescription$ Return target creature to its owner's hand.
SVar:DBAddendum:DB$ Draw | NumCards$ 1 | ConditionPlayerTurn$ True | ConditionPhases$ Main1,Main2 | SpellDescription$ Addendum - If you cast this spell during your main phase, draw a card.
SVar:DBAddendum:DB$ Draw | NumCards$ 1 | ConditionPlayerTurn$ True | ConditionPhases$ Main1,Main2 | ConditionDefined$ Self | ConditionPresent$ Card.wasCast | SpellDescription$ Addendum - If you cast this spell during your main phase, draw a card.
Oracle:Return target creature to its owner's hand.\nAddendum — If you cast this spell during your main phase, draw a card.
......@@ -2,5 +2,5 @@ Name:Arrester's Zeal
ManaCost:W
Types:Instant
A:SP$ Pump | Cost$ W | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | NumDef$ +2 | SubAbility$ DBAddendum | SpellDescription$ Target creature gets +2/+2 until end of turn.
SVar:DBAddendum:DB$ Pump | Cost$ W | Defined$ Targeted | ConditionPlayerTurn$ True | ConditionPhases$ Main1,Main2 | SpellDescription$ Addendum - If you cast this spell during your main phase, that creature gains flying until end of turn.
SVar:DBAddendum:DB$ Pump | Defined$ Targeted | ConditionPlayerTurn$ True | ConditionPhases$ Main1,Main2 | ConditionDefined$ Self | ConditionPresent$ Card.wasCast | KW$ Flying | SpellDescription$ Addendum - If you cast this spell during your main phase, that creature gains flying until end of turn.
Oracle:Target creature gets +2/+2 until end of turn.\nAddendum — If you cast this spell during your main phase, that creature gains flying until end of turn.
Name:Blinding Radiance
ManaCost:2 W
Types:Sorcery
A:SP$ TapAll | Cost$ 2 W | ValidCards$ Creature.OppCtrl+toughnessLE2 | SpellDescription$ Tap all creatures your opponents control with toughness 2 or less.
Oracle:Tap all creatures your opponents control with toughness 2 or less.