Merge remote-tracking branch 'upstream/master'

This commit is contained in:
CCTV-1
2020-04-06 18:12:05 +08:00
68 changed files with 894 additions and 255 deletions

View File

@@ -31,6 +31,7 @@ import forge.game.ability.ApiType;
import forge.game.card.*; import forge.game.card.*;
import forge.game.card.CardPlayOption.PayManaCost; import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController; import forge.game.player.PlayerController;
@@ -218,23 +219,34 @@ public final class GameActionUtil {
} }
} }
if (!sa.isBasicSpell()) { // below are for some special cases of activated abilities
return alternatives;
}
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) { if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
// set the cost to this directly to buypass non mana cost
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
newSA.setActivatingPlayer(activator);
newSA.setBasicSpell(false);
newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
// makes new SpellDescription
final StringBuilder sb = new StringBuilder();
sb.append(newSA.getCostDescription());
sb.append(newSA.getParam("SpellDescription"));
newSA.setDescription(sb.toString());
alternatives.add(newSA); for (final KeywordInterface inst : source.getKeywords()) {
// need to find the correct Keyword from which this Ability is from
if (!inst.getAbilities().contains(sa)) {
continue;
}
// set the cost to this directly to buypass non mana cost
final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
newSA.setActivatingPlayer(activator);
newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
// need to build a new Keyword to get better Reminder Text
String data[] = inst.getOriginal().split(":");
data[1] = "0";
KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
// makes new SpellDescription
final StringBuilder sb = new StringBuilder();
sb.append(newSA.getCostDescription());
sb.append("(").append(newKi.getReminderText()).append(")");
newSA.setDescription(sb.toString());
alternatives.add(newSA);
break;
}
} }
if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) { if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) {

View File

@@ -150,6 +150,19 @@ public class CountersPutEffect extends SpellAbilityEffect {
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense)); CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
tgtCards.addAll(pc.chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false)); tgtCards.addAll(pc.chooseCardsForEffect(leastToughness, sa, Localizer.getInstance().getMessage("lblChooseACreatureWithLeastToughness"), 1, 1, false));
tgtObjects.addAll(tgtCards); tgtObjects.addAll(tgtCards);
} else if (sa.hasParam("Choices")) {
ZoneType choiceZone = ZoneType.Battlefield;
if (sa.hasParam("ChoiceZone")) {
choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
CardCollection choices = new CardCollection(game.getCardsIn(choiceZone));
int n = sa.hasParam("ChoiceAmount") ? Integer.parseInt(sa.getParam("ChoiceAmount")) : 1;
choices = CardLists.getValidCards(choices, sa.getParam("Choices"), activator, card);
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " ";
tgtObjects.addAll(new CardCollection(pc.chooseCardsForEffect(choices, sa, title, n, n, !sa.hasParam("ChoiceOptional"))));
} else { } else {
tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined")); tgtObjects.addAll(getDefinedOrTargeted(sa, "Defined"));
} }

View File

@@ -129,6 +129,8 @@ public class Card extends GameEntity implements Comparable<Card> {
private final Multimap<Long, Keyword> cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build(); private final Multimap<Long, Keyword> cantHaveKeywords = MultimapBuilder.hashKeys().enumSetValues(Keyword.class).build();
private final Map<CounterType, Long> counterTypeTimestamps = Maps.newEnumMap(CounterType.class);
private final Map<Long, Integer> canBlockAdditional = Maps.newTreeMap(); private final Map<Long, Integer> canBlockAdditional = Maps.newTreeMap();
private final Set<Long> canBlockAny = Sets.newHashSet(); private final Set<Long> canBlockAny = Sets.newHashSet();
@@ -1305,12 +1307,44 @@ public class Card extends GameEntity implements Comparable<Card> {
getController().addCounterToPermThisTurn(counterType, addAmount); getController().addCounterToPermThisTurn(counterType, addAmount);
view.updateCounters(this); view.updateCounters(this);
} }
if (newValue <= 0) {
removeCounterTimestamp(counterType);
} else {
addCounterTimestamp(counterType);
}
if (table != null) { if (table != null) {
table.put(this, counterType, addAmount); table.put(this, counterType, addAmount);
} }
return addAmount; return addAmount;
} }
public boolean addCounterTimestamp(CounterType counterType) {
return addCounterTimestamp(counterType, true);
}
public boolean addCounterTimestamp(CounterType counterType, boolean updateView) {
if (!counterType.isKeywordCounter()) {
return false;
}
removeCounterTimestamp(counterType);
long timestamp = game.getNextTimestamp();
counterTypeTimestamps.put(counterType, timestamp);
addChangedCardKeywords(ImmutableList.of(counterType.getKeyword().toString()), null, false, false, timestamp, updateView);
return true;
}
public boolean removeCounterTimestamp(CounterType counterType) {
return removeCounterTimestamp(counterType, true);
}
public boolean removeCounterTimestamp(CounterType counterType, boolean updateView) {
Long old = counterTypeTimestamps.remove(counterType);
if (old != null) {
removeChangedCardKeywords(old, updateView);
}
return old != null;
}
/** /**
* <p> * <p>
* addCountersAddedBy. * addCountersAddedBy.
@@ -1358,6 +1392,10 @@ public class Card extends GameEntity implements Comparable<Card> {
setCounters(counterName, newValue); setCounters(counterName, newValue);
view.updateCounters(this); view.updateCounters(this);
if (newValue <= 0) {
this.removeCounterTimestamp(counterName);
}
//fire card stats changed event if p/t bonuses or loyalty changed from subtracted counters //fire card stats changed event if p/t bonuses or loyalty changed from subtracted counters
if (powerBonusBefore != getPowerBonusFromCounters() || toughnessBonusBefore != getToughnessBonusFromCounters() || loyaltyBefore != getCurrentLoyalty()) { if (powerBonusBefore != getPowerBonusFromCounters() || toughnessBonusBefore != getToughnessBonusFromCounters() || loyaltyBefore != getCurrentLoyalty()) {
getGame().fireEvent(new GameEventCardStatsChanged(this)); getGame().fireEvent(new GameEventCardStatsChanged(this));
@@ -1380,8 +1418,23 @@ public class Card extends GameEntity implements Comparable<Card> {
@Override @Override
public final void setCounters(final Map<CounterType, Integer> allCounters) { public final void setCounters(final Map<CounterType, Integer> allCounters) {
boolean changed = false;
for (CounterType ct : counters.keySet()) {
if (removeCounterTimestamp(ct, false)) {
changed = true;
}
}
counters = allCounters; counters = allCounters;
view.updateCounters(this); view.updateCounters(this);
for (CounterType ct : counters.keySet()) {
if (addCounterTimestamp(ct, false)) {
changed = true;
}
}
if (changed) {
updateKeywords();
}
} }
@Override @Override
@@ -1389,6 +1442,16 @@ public class Card extends GameEntity implements Comparable<Card> {
if (counters.isEmpty()) { return; } if (counters.isEmpty()) { return; }
counters.clear(); counters.clear();
view.updateCounters(this); view.updateCounters(this);
boolean changed = false;
for (CounterType ct : counterTypeTimestamps.keySet()) {
if (removeCounterTimestamp(ct, false)) {
changed = true;
}
}
if (changed) {
updateKeywords();
}
} }
public final String getSVar(final String var) { public final String getSVar(final String var) {

View File

@@ -20,6 +20,8 @@ package forge.game.card;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import forge.game.keyword.Keyword;
/** /**
* The class Counters. * The class Counters.
* *
@@ -309,7 +311,23 @@ public enum CounterType {
EXPERIENCE("EXP"), EXPERIENCE("EXP"),
POISON("POISN"); POISON("POISN"),
// Keyword Counters
FLYING("Flying"),
FIRST_STRIKE("First Strike"),
DOUBLE_STRIKE("Double Strike"),
DEATHTOUCH("Deathtouch"),
HEXPROOF("Hexproof"),
INDESTRUCTIBLE("Indestructible"),
LIFELINK("Lifelink"),
MENACE("Menace"),
REACH("Reach"),
TRAMPLE("Trample"),
VIGILANCE("Vigilance")
;
private String name, counterOnCardDisplayName; private String name, counterOnCardDisplayName;
private int red, green, blue; private int red, green, blue;
@@ -365,6 +383,39 @@ public enum CounterType {
return Enum.valueOf(CounterType.class, replacedName); return Enum.valueOf(CounterType.class, replacedName);
} }
public boolean isKeywordCounter() {
return this.getKeyword() != null;
}
public Keyword getKeyword() {
switch (this) {
case FLYING:
return Keyword.FLYING;
case FIRST_STRIKE:
return Keyword.FIRST_STRIKE;
case DOUBLE_STRIKE:
return Keyword.DOUBLE_STRIKE;
case DEATHTOUCH:
return Keyword.DEATHTOUCH;
case HEXPROOF:
return Keyword.HEXPROOF;
case INDESTRUCTIBLE:
return Keyword.INDESTRUCTIBLE;
case LIFELINK:
return Keyword.LIFELINK;
case MENACE:
return Keyword.MENACE;
case REACH:
return Keyword.REACH;
case TRAMPLE:
return Keyword.TRAMPLE;
case VIGILANCE:
return Keyword.VIGILANCE;
default:
return null;
}
}
public static final ImmutableList<CounterType> values = ImmutableList.copyOf(values()); public static final ImmutableList<CounterType> values = ImmutableList.copyOf(values());
} }

View File

@@ -104,13 +104,6 @@
<artifactId>gdx-backend-android</artifactId> <artifactId>gdx-backend-android</artifactId>
<version>1.9.10</version> <version>1.9.10</version>
</dependency> </dependency>
<dependency>
<groupId>com.android.support</groupId>
<artifactId>support-v4</artifactId>
<version>23.1.1</version>
<scope>system</scope>
<systemPath>${pom.basedir}/libs/android-support-v4.jar</systemPath>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View File

@@ -25,7 +25,6 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.support.v4.content.ContextCompat;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.view.Gravity; import android.view.Gravity;
@@ -77,7 +76,7 @@ public class Main extends AndroidApplication {
text.setTypeface(Typeface.SERIF); text.setTypeface(Typeface.SERIF);
String title="Forge needs Storage Permission to run properly...\n" + String title="Forge needs Storage Permission to run properly...\n" +
"Follow this simple steps below:\n\n"; "Follow these simple steps:\n\n";
String steps = " 1) Tap \"Open App Details\" Button.\n" + String steps = " 1) Tap \"Open App Details\" Button.\n" +
" 2) Tap Permissions\n"+ " 2) Tap Permissions\n"+
" 3) Turn on the Storage Permission.\n\n"+ " 3) Turn on the Storage Permission.\n\n"+
@@ -145,10 +144,16 @@ public class Main extends AndroidApplication {
super.onBackPressed(); super.onBackPressed();
} }
private boolean checkPermission() { private boolean checkPermission() {
int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); int pid = android.os.Process.myPid();
if (result == PackageManager.PERMISSION_GRANTED) { int uid = android.os.Process.myUid();
return true; try {
} else { int result = this.getBaseContext().checkPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, pid, uid);
if (result == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
return false;
}
} catch (NullPointerException e) {
return false; return false;
} }
} }
@@ -218,7 +223,7 @@ public class Main extends AndroidApplication {
} }
ForgePreferences prefs = FModel.getPreferences(); ForgePreferences prefs = FModel.getPreferences();
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, propertyConfig)); initialize(Forge.getApp(new AndroidClipboard(), adapter, assetsDir, propertyConfig));
} }

View File

@@ -220,8 +220,8 @@ public enum FControl implements KeyEventDispatcher {
final ForgePreferences prefs = FModel.getPreferences(); final ForgePreferences prefs = FModel.getPreferences();
//set ElsaSerializer from preference //set ExperimentalNetworkOption from preference
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
GuiBase.enablePropertyConfig(propertyConfig); GuiBase.enablePropertyConfig(propertyConfig);
closeAction = CloseAction.valueOf(prefs.getPref(FPref.UI_CLOSE_ACTION)); closeAction = CloseAction.valueOf(prefs.getPref(FPref.UI_CLOSE_ACTION));

View File

@@ -95,6 +95,19 @@ public enum CSubmenuPreferences implements ICDoc {
} }
}); });
// This updates Experimental Network Option
view.getCbUseExperimentalNetworkStream().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent arg0) {
if (updating) { return; }
final boolean toggle = view.getCbUseExperimentalNetworkStream().isSelected();
GuiBase.enablePropertyConfig(toggle);
prefs.setPref(FPref.UI_NETPLAY_COMPAT, String.valueOf(toggle));
prefs.save();
}
});
lstControls.clear(); // just in case lstControls.clear(); // just in case
lstControls.add(Pair.of(view.getCbAnte(), FPref.UI_ANTE)); lstControls.add(Pair.of(view.getCbAnte(), FPref.UI_ANTE));
lstControls.add(Pair.of(view.getCbAnteMatchRarity(), FPref.UI_ANTE_MATCH_RARITY)); lstControls.add(Pair.of(view.getCbAnteMatchRarity(), FPref.UI_ANTE_MATCH_RARITY));
@@ -115,7 +128,7 @@ public enum CSubmenuPreferences implements ICDoc {
lstControls.add(Pair.of(view.getCbSingletons(), FPref.DECKGEN_SINGLETONS)); lstControls.add(Pair.of(view.getCbSingletons(), FPref.DECKGEN_SINGLETONS));
lstControls.add(Pair.of(view.getCbEnableAICheats(), FPref.UI_ENABLE_AI_CHEATS)); lstControls.add(Pair.of(view.getCbEnableAICheats(), FPref.UI_ENABLE_AI_CHEATS));
lstControls.add(Pair.of(view.getCbEnableUnknownCards(), FPref.UI_LOAD_UNKNOWN_CARDS)); lstControls.add(Pair.of(view.getCbEnableUnknownCards(), FPref.UI_LOAD_UNKNOWN_CARDS));
lstControls.add(Pair.of(view.getCbUseElsa(), FPref.UI_USE_ELSA)); lstControls.add(Pair.of(view.getCbUseExperimentalNetworkStream(), FPref.UI_NETPLAY_COMPAT));
lstControls.add(Pair.of(view.getCbImageFetcher(), FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER)); lstControls.add(Pair.of(view.getCbImageFetcher(), FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER));
lstControls.add(Pair.of(view.getCbDisplayFoil(), FPref.UI_OVERLAY_FOIL_EFFECT)); lstControls.add(Pair.of(view.getCbDisplayFoil(), FPref.UI_OVERLAY_FOIL_EFFECT));
lstControls.add(Pair.of(view.getCbRandomFoil(), FPref.UI_RANDOM_FOIL)); lstControls.add(Pair.of(view.getCbRandomFoil(), FPref.UI_RANDOM_FOIL));
@@ -255,6 +268,7 @@ public enum CSubmenuPreferences implements ICDoc {
setPlayerNameButtonText(); setPlayerNameButtonText();
view.getCbDevMode().setSelected(ForgePreferences.DEV_MODE); view.getCbDevMode().setSelected(ForgePreferences.DEV_MODE);
view.getCbEnableMusic().setSelected(prefs.getPrefBoolean(FPref.UI_ENABLE_MUSIC)); view.getCbEnableMusic().setSelected(prefs.getPrefBoolean(FPref.UI_ENABLE_MUSIC));
view.getCbUseExperimentalNetworkStream().setSelected(prefs.getPrefBoolean(FPref.UI_NETPLAY_COMPAT));
for(final Pair<JCheckBox, FPref> kv: lstControls) { for(final Pair<JCheckBox, FPref> kv: lstControls) {
kv.getKey().setSelected(prefs.getPrefBoolean(kv.getValue())); kv.getKey().setSelected(prefs.getPrefBoolean(kv.getValue()));

View File

@@ -108,7 +108,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final JCheckBox cbRemindOnPriority = new OptionsCheckBox(localizer.getMessage("cbRemindOnPriority")); private final JCheckBox cbRemindOnPriority = new OptionsCheckBox(localizer.getMessage("cbRemindOnPriority"));
private final JCheckBox cbUseSentry = new OptionsCheckBox(localizer.getMessage("cbUseSentry")); private final JCheckBox cbUseSentry = new OptionsCheckBox(localizer.getMessage("cbUseSentry"));
private final JCheckBox cbEnableUnknownCards = new OptionsCheckBox(localizer.getMessage("lblEnableUnknownCards")); private final JCheckBox cbEnableUnknownCards = new OptionsCheckBox(localizer.getMessage("lblEnableUnknownCards"));
private final JCheckBox cbUseElsa = new OptionsCheckBox("Use ELSA Serializer"); private final JCheckBox cbUseExperimentalNetworkStream = new OptionsCheckBox("Experimental Network Compatibility");
private final Map<FPref, KeyboardShortcutField> shortcutFields = new HashMap<>(); private final Map<FPref, KeyboardShortcutField> shortcutFields = new HashMap<>();
@@ -292,8 +292,8 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbEnableUnknownCards, titleConstraints); pnlPrefs.add(cbEnableUnknownCards, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableUnknownCards")), descriptionConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableUnknownCards")), descriptionConstraints);
/*pnlPrefs.add(cbUseElsa, titleConstraints); pnlPrefs.add(cbUseExperimentalNetworkStream, titleConstraints);
pnlPrefs.add(new NoteLabel("Use ELSA Serializer for Network (EXPERIMENTAL Option, Requires restart)"), descriptionConstraints);*/ pnlPrefs.add(new NoteLabel("Forge switches to compatible network stream. (If unsure, turn OFF this option)"), descriptionConstraints);
// Graphic Options // Graphic Options
pnlPrefs.add(new SectionLabel(localizer.getMessage("GraphicOptions")), sectionConstraints + ", gaptop 2%"); pnlPrefs.add(new SectionLabel(localizer.getMessage("GraphicOptions")), sectionConstraints + ", gaptop 2%");
@@ -594,8 +594,8 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
} }
/** @return {@link javax.swing.JCheckBox} */ /** @return {@link javax.swing.JCheckBox} */
public JCheckBox getCbUseElsa() { public JCheckBox getCbUseExperimentalNetworkStream() {
return cbUseElsa; return cbUseExperimentalNetworkStream;
} }
/** @return {@link javax.swing.JCheckBox} */ /** @return {@link javax.swing.JCheckBox} */

View File

@@ -32,7 +32,7 @@ public class Main extends IOSApplication.Delegate {
config.useAccelerometer = false; config.useAccelerometer = false;
config.useCompass = false; config.useCompass = false;
ForgePreferences prefs = FModel.getPreferences(); ForgePreferences prefs = FModel.getPreferences();
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir, propertyConfig); final ApplicationListener app = Forge.getApp(new IOSClipboard(), new IOSAdapter(), assetsDir, propertyConfig);
final IOSApplication iosApp = new IOSApplication(app, config); final IOSApplication iosApp = new IOSApplication(app, config);
return iosApp; return iosApp;

View File

@@ -95,7 +95,7 @@ public class Main {
config.useHDPI = desktopMode; // enable HiDPI on Mac OS config.useHDPI = desktopMode; // enable HiDPI on Mac OS
ForgePreferences prefs = FModel.getPreferences(); ForgePreferences prefs = FModel.getPreferences();
boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_USE_ELSA); boolean propertyConfig = prefs != null && prefs.getPrefBoolean(ForgePreferences.FPref.UI_NETPLAY_COMPAT);
new LwjglApplication(Forge.getApp(new LwjglClipboard(), new DesktopAdapter(switchOrientationFile), new LwjglApplication(Forge.getApp(new LwjglClipboard(), new DesktopAdapter(switchOrientationFile),
desktopMode ? desktopModeAssetsDir : assetsDir, propertyConfig), config); desktopMode ? desktopModeAssetsDir : assetsDir, propertyConfig), config);
} }

View File

@@ -1,5 +1,6 @@
package forge; package forge;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.Input.Keys;
@@ -87,6 +88,7 @@ public class Forge implements ApplicationListener {
//install our error handler //install our error handler
ExceptionHandler.registerErrorHandling(); ExceptionHandler.registerErrorHandling();
GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android);
graphics = new Graphics(); graphics = new Graphics();
splashScreen = new SplashScreen(); splashScreen = new SplashScreen();
frameRate = new FrameRate(); frameRate = new FrameRate();

View File

@@ -113,23 +113,28 @@ public class MatchScreen extends FScreen {
if (MatchController.instance.getLocalPlayerCount() <= 1 || MatchController.instance.hotSeatMode()) { if (MatchController.instance.getLocalPlayerCount() <= 1 || MatchController.instance.hotSeatMode()) {
topPlayerPrompt = null; topPlayerPrompt = null;
} }
else { //show top prompt if multiple human players and not playing in Hot Seat mode else {
topPlayerPrompt = add(new VPrompt("", "", if (GuiBase.isNetworkplay()) {
new FEventHandler() { topPlayerPrompt = null;
@Override } else {
public void handleEvent(FEvent e) { //show top prompt if multiple human players and not playing in Hot Seat mode and not in network play
getGameController().selectButtonOk(); topPlayerPrompt = add(new VPrompt("", "",
} new FEventHandler() {
}, @Override
new FEventHandler() { public void handleEvent(FEvent e) {
@Override getGameController().selectButtonOk();
public void handleEvent(FEvent e) { }
getGameController().selectButtonCancel(); },
} new FEventHandler() {
})); @Override
topPlayerPrompt.setRotate180(true); public void handleEvent(FEvent e) {
topPlayerPanel.setRotate180(true); getGameController().selectButtonCancel();
getHeader().setRotate90(true); }
}));
topPlayerPrompt.setRotate180(true);
topPlayerPanel.setRotate180(true);
getHeader().setRotate90(true);
}
} }
gameMenu = new VGameMenu(); gameMenu = new VGameMenu();

View File

@@ -2,6 +2,7 @@ package forge.screens.online;
import forge.FThreads; import forge.FThreads;
import forge.Forge; import forge.Forge;
import forge.assets.FSkinProp;
import forge.interfaces.ILobbyView; import forge.interfaces.ILobbyView;
import forge.match.GameLobby; import forge.match.GameLobby;
import forge.net.ChatMessage; import forge.net.ChatMessage;
@@ -10,15 +11,55 @@ import forge.net.IOnlineLobby;
import forge.net.NetConnectUtil; import forge.net.NetConnectUtil;
import forge.net.OfflineLobby; import forge.net.OfflineLobby;
import forge.net.client.FGameClient; import forge.net.client.FGameClient;
import forge.properties.ForgeConstants;
import forge.screens.LoadingOverlay; import forge.screens.LoadingOverlay;
import forge.screens.constructed.LobbyScreen; import forge.screens.constructed.LobbyScreen;
import forge.screens.online.OnlineMenu.OnlineScreen; import forge.screens.online.OnlineMenu.OnlineScreen;
import forge.util.gui.SOptionPane;
public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby {
public OnlineLobbyScreen() { public OnlineLobbyScreen() {
super(null, OnlineMenu.getMenu(), new OfflineLobby()); super(null, OnlineMenu.getMenu(), new OfflineLobby());
} }
private static GameLobby gameLobby;
public GameLobby getGameLobby() {
return gameLobby;
}
public static void clearGameLobby() {
gameLobby = null;
}
public static void setGameLobby(GameLobby gameLobby) {
OnlineLobbyScreen.gameLobby = gameLobby;
}
private static FGameClient fGameClient;
public static FGameClient getfGameClient() {
return fGameClient;
}
public static void closeClient() {
getfGameClient().close();
fGameClient = null;
}
public void closeConn(String msg) {
clearGameLobby();
Forge.back();
if (msg.length() > 0) {
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
SOptionPane.showMessageDialog(msg, "Error", FSkinProp.ICO_WARNING);
}
});
}
}
@Override @Override
public ILobbyView setLobby(GameLobby lobby0) { public ILobbyView setLobby(GameLobby lobby0) {
initLobby(lobby0); initLobby(lobby0);
@@ -27,13 +68,13 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby {
@Override @Override
public void setClient(FGameClient client) { public void setClient(FGameClient client) {
// TODO Auto-generated method stub fGameClient = client;
} }
@Override @Override
public void onActivate() { public void onActivate() {
if (getLobby() instanceof OfflineLobby) { if (getGameLobby() == null) {
setGameLobby(getLobby());
//prompt to connect to server when offline lobby activated //prompt to connect to server when offline lobby activated
FThreads.invokeInBackgroundThread(new Runnable() { FThreads.invokeInBackgroundThread(new Runnable() {
@Override @Override
@@ -43,7 +84,7 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby {
@Override @Override
public void run() { public void run() {
if (url == null) { if (url == null) {
Forge.back(); //go back to previous screen if user cancels connection closeConn(""); //go back to previous screen if user cancels connection
return; return;
} }
@@ -56,6 +97,10 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby {
final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen();
if (joinServer) { if (joinServer) {
result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface);
if (result.getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception
closeConn("Invalid host address (" + url + ") was detected.");
return;
}
} }
else { else {
result = NetConnectUtil.host(OnlineLobbyScreen.this, chatInterface); result = NetConnectUtil.host(OnlineLobbyScreen.this, chatInterface);

View File

@@ -6,17 +6,21 @@ import forge.assets.FSkinImage;
import forge.menu.FMenuItem; import forge.menu.FMenuItem;
import forge.menu.FPopupMenu; import forge.menu.FPopupMenu;
import forge.model.FModel; import forge.model.FModel;
import forge.net.server.FServerManager;
import forge.properties.ForgePreferences; import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
import forge.screens.FScreen; import forge.screens.FScreen;
import forge.toolbox.FEvent; import forge.toolbox.FEvent;
import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FEvent.FEventHandler;
import forge.toolbox.FOptionPane;
import forge.util.Callback;
import forge.util.Localizer; import forge.util.Localizer;
public class OnlineMenu extends FPopupMenu { public class OnlineMenu extends FPopupMenu {
public enum OnlineScreen { public enum OnlineScreen {
Lobby("lblLobby", FSkinImage.FAVICON, OnlineLobbyScreen.class), Lobby("lblLobby", FSkinImage.FAVICON, OnlineLobbyScreen.class),
Chat("lblChat", FSkinImage.QUEST_NOTES, OnlineChatScreen.class); Chat("lblChat", FSkinImage.QUEST_NOTES, OnlineChatScreen.class);
Disconnect("Disconnect", FSkinImage.EXILE, null);
private final FMenuItem item; private final FMenuItem item;
private final Class<? extends FScreen> screenClass; private final Class<? extends FScreen> screenClass;
@@ -27,6 +31,30 @@ public class OnlineMenu extends FPopupMenu {
item = new FMenuItem(Localizer.getInstance().getMessage(caption0), icon0, new FEventHandler() { item = new FMenuItem(Localizer.getInstance().getMessage(caption0), icon0, new FEventHandler() {
@Override @Override
public void handleEvent(FEvent e) { public void handleEvent(FEvent e) {
if(screenClass == null) {
FOptionPane.showConfirmDialog(
"Leave lobby? Doing so will shut down all connections and stop hosting.",
"Disconnect", new Callback<Boolean>() {
@Override
public void run(Boolean result) {
if (result) {
if (FServerManager.getInstance() != null)
if(FServerManager.getInstance().isHosting()) {
FServerManager.getInstance().unsetReady();
FServerManager.getInstance().stopServer();
}
if (OnlineLobbyScreen.getfGameClient() != null)
OnlineLobbyScreen.closeClient();
Forge.back();
screen = null;
OnlineLobbyScreen.clearGameLobby();
}
}
});
return;
}
Forge.back(); //remove current screen from chain Forge.back(); //remove current screen from chain
open(); open();
setPreferredScreen(OnlineScreen.this); setPreferredScreen(OnlineScreen.this);

View File

@@ -3,6 +3,7 @@ package forge.screens.settings;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import forge.Forge; import forge.Forge;
import forge.Graphics; import forge.Graphics;
import forge.GuiBase;
import forge.MulliganDefs; import forge.MulliganDefs;
import forge.StaticData; import forge.StaticData;
import forge.ai.AiProfileUtil; import forge.ai.AiProfileUtil;
@@ -234,12 +235,35 @@ public class SettingsPage extends TabPage<SettingsScreen> {
3); 3);
lstSettings.addItem(new BooleanSetting(FPref.UI_LOAD_UNKNOWN_CARDS, lstSettings.addItem(new BooleanSetting(FPref.UI_LOAD_UNKNOWN_CARDS,
localizer.getMessage("lblEnableUnknownCards"), localizer.getMessage("lblEnableUnknownCards"),
localizer.getMessage("nlEnableUnknownCards")), localizer.getMessage("nlEnableUnknownCards")) {
@Override
public void select() {
super.select();
FOptionPane.showConfirmDialog(
localizer.getMessage("lblRestartForgeDescription"),
localizer.getMessage("lblRestartForge"),
localizer.getMessage("lblRestart"),
localizer.getMessage("lblLater"), new Callback<Boolean>() {
@Override
public void run(Boolean result) {
if (result) {
Forge.restart(true);
}
}
});
}
},
3);
lstSettings.addItem(new BooleanSetting(FPref.UI_NETPLAY_COMPAT,
"Experimental Network Compatibility",
"Forge switches to compatible network stream. (If unsure, turn OFF this option)") {
@Override
public void select() {
super.select();
GuiBase.enablePropertyConfig(FModel.getPreferences().getPrefBoolean(FPref.UI_NETPLAY_COMPAT));
}
},
3); 3);
/*lstSettings.addItem(new BooleanSetting(FPref.UI_USE_ELSA,
"Use ELSA Serializer",
"Use ELSA Serializer for Network (EXPERIMENTAL Option, Requires restart)"),
3);*/
//Graphic Options //Graphic Options
lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER, lstSettings.addItem(new BooleanSetting(FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER,

View File

@@ -431,6 +431,7 @@ Regen - regeneration.wav - triggered when a creature is regenerated.
RemoveCounter - remove_counter.wav - triggered when a counter is removed from a permanent. RemoveCounter - remove_counter.wav - triggered when a counter is removed from a permanent.
Sacrifice - sacrifice.wav - triggered when a permanent is sacrificed. Sacrifice - sacrifice.wav - triggered when a permanent is sacrificed.
Sorcery [*] - sorcery.wav - triggered when a sorcery is played. Sorcery [*] - sorcery.wav - triggered when a sorcery is played.
StartOfDuel - start_duel.wav - triggered when a duel starts
Shuffle [*] - shuffle.wav - triggered when a player shuffles his deck. Shuffle [*] - shuffle.wav - triggered when a player shuffles his deck.
Tap [*] - tap.wav - triggered when a permanent is tapped. Tap [*] - tap.wav - triggered when a permanent is tapped.
Token [*] - token.wav - triggered when a token is created. Token [*] - token.wav - triggered when a token is created.

View File

@@ -77,11 +77,6 @@
<artifactId>slf4j-simple</artifactId> <artifactId>slf4j-simple</artifactId>
<version>1.7.22</version> <version>1.7.22</version>
</dependency> </dependency>
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>elsa</artifactId>
<version>3.0.0-M7</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,13 +1,9 @@
Name:Dismantle Name:Dismantle
ManaCost:2 R ManaCost:2 R
Types:Sorcery Types:Sorcery
A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | RememberTargets$ True | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. A:SP$ Destroy | Cost$ 2 R | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SubAbility$ DBChoice | SpellDescription$ Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control.
SVar:DBChoice:DB$ GenericChoice | Choices$ DBP1P1,DBCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control. SVar:DBChoice:DB$ GenericChoice | Choices$ DBPutP1P1,DBPutCharge | ConditionDefined$ Targeted | ConditionPresent$ Card.HasCounters | ConditionCompare$ GE1 | StackDescription$ put that many +1/+1 counters or charge counters on an artifact you control.
SVar:DBP1P1:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ +1/+1 | SubAbility$ DBPutP1P1 SVar:DBPutP1P1:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ P1P1 | CounterNum$ X | References$ X | SpellDescription$ +1/+1
SVar:DBPutP1P1:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ X | References$ X | SubAbility$ DBCleanup SVar:DBPutCharge:DB$ PutCounter | Choices$ Artifact.YouCtrl | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge
SVar:DBCharge:DB$ ChooseCard | Choices$ Artifact.YouCtrl | Amount$ 1 | SpellDescription$ charge | SubAbility$ DBPutCharge SVar:X:TargetedLKI$CardCounters.ALL
SVar:DBPutCharge:DB$ PutCounter | Defined$ ChosenCard | CounterType$ CHARGE | CounterNum$ X | References$ X | SpellDescription$ Charge | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:RememberedLKI$CardCounters.ALL
SVar:Picture:http://www.wizards.com/global/images/magic/general/dismantle.jpg
Oracle:Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control. Oracle:Destroy target artifact. If that artifact had counters on it, put that many +1/+1 counters or charge counters on an artifact you control.

View File

@@ -10,9 +10,7 @@ ALTERNATE
Name:Finality Name:Finality
ManaCost:4 B G ManaCost:4 B G
Types:Sorcery Types:Sorcery
A:SP$ ChooseCard | Cost$ 4 B G | Defined$ You | Amount$ 1 | MinAmount$ 0 | Choices$ Creature.YouCtrl | SubAbility$ DBPutCounter | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. A:SP$ PutCounter | Cost$ 4 B G | Choices$ Creature.YouCtrl | ChoiceOptional$ True | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll | StackDescription$ SpellDescription | SpellDescription$ You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn.
SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBPumpAll SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True
SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -4 | NumDef$ -4 | IsCurse$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn. Oracle:You may put two +1/+1 counters on a creature you control. Then all creatures get -4/-4 until end of turn.

View File

@@ -1,12 +1,10 @@
Name:Haphazard Bombardment Name:Haphazard Bombardment
ManaCost:5 R ManaCost:5 R
Types:Enchantment Types:Enchantment
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them.
SVar:TrigChoose:DB$ ChooseCard | Defined$ You | Amount$ 4 | Choices$ Permanent.YouDontCtrl+nonEnchantment | SubAbility$ DBPutCounter | AILogic$ AtLeast1 | Mandatory$ True SVar:DBPutCounter:DB$ PutCounter | Choices$ Permanent.YouDontCtrl+nonEnchantment | ChoiceAmount§ 4 | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1
SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ AIM | CounterNum$ 1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Permanent.YouDontCtrl+counters_GE1_AIM | PresentCompare$ GE2 | Execute$ TrigDestroy | TriggerDescription$ At the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Permanent.YouDontCtrl+counters_GE1_AIM | PresentCompare$ GE2 | Execute$ TrigDestroy | TriggerDescription$ At the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random.
SVar:TrigDestroy:DB$ ChooseCard | Amount$ 1 | AtRandom$ True | Choices$ Permanent.YouDontCtrl+counters_GE1_AIM | SubAbility$ DBDestroy SVar:TrigDestroy:DB$ ChooseCard | Amount$ 1 | AtRandom$ True | Choices$ Permanent.YouDontCtrl+counters_GE1_AIM | SubAbility$ DBDestroy
SVar:DBDestroy:DB$ Destroy | Defined$ ChosenCard | SubAbility$ DBCleanup SVar:DBDestroy:DB$ Destroy | Defined$ ChosenCard | SubAbility$ DBCleanup
SVar:Picture:http://www.wizards.com/global/images/magic/general/haphazard_bombardment.jpg SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True
Oracle:When Haphazard Bombardment enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them.\nAt the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random. Oracle:When Haphazard Bombardment enters the battlefield, choose four nonenchantment permanents you don't control and put an aim counter on each of them.\nAt the beginning of your end step, if two or more permanents you don't control have an aim counter on them, destroy one of those permanents at random.

View File

@@ -1,7 +1,6 @@
Name:Settle the Score Name:Settle the Score
ManaCost:2 B B ManaCost:2 B B
Types:Sorcery Types:Sorcery
A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBChoice | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control. A:SP$ ChangeZone | Cost$ 2 B B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBPutLoyalty | SpellDescription$ Exile target creature. Put two loyalty counters on a planeswalker you control.
SVar:DBChoice:DB$ ChooseCard | Choices$ Planeswalker.YouCtrl | Amount$ 1 | Mandatory$ True | SubAbility$ DBPutLoyalty SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ 2
SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ 2
Oracle:Exile target creature. Put two loyalty counters on a planeswalker you control. Oracle:Exile target creature. Put two loyalty counters on a planeswalker you control.

View File

@@ -1,9 +1,8 @@
Name:The Elderspell Name:The Elderspell
ManaCost:B B ManaCost:B B
Types:Sorcery Types:Sorcery
A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBChooseCard | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way. A:SP$ Destroy | Cost$ B B | ValidTgts$ Planeswalker | TgtPrompt$ Select target planeswalker | TargetMin$ 0 | TargetMax$ MaxTargets | References$ MaxTargets | SubAbility$ DBPutLoyalty | RememberDestroyed$ True | SpellDescription$ Destroy any number of target planeswalkers. Choose a planeswalker you control. Put two loyalty counters on it for each planeswalker destroyed this way.
SVar:DBChooseCard:DB$ ChooseCard | Defined$ You | Amount$ 1 | Choices$ Planeswalker.YouCtrl | Mandatory$ True | SubAbility$ DBPutLoyalty SVar:DBPutLoyalty:DB$ PutCounter | Choices$ Planeswalker.YouCtrl | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup
SVar:DBPutLoyalty:DB$ PutCounter | Defined$ ChosenCard | CounterType$ LOYALTY | CounterNum$ X | References$ X | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:MaxTargets:Count$Valid Planeswalker SVar:MaxTargets:Count$Valid Planeswalker
SVar:X:Count$RememberedSize/Twice SVar:X:Count$RememberedSize/Twice

View File

@@ -0,0 +1,11 @@
Name:Jirina Kudro
ManaCost:1 R W B
Types:Legendary Creature Human Soldier
PT:3/3
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 white Human Soldier creature token for each time you've cast a commander from the command zone this game.
SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ w_1_1_human | TokenOwner$ You | LegacyImage$ w 1 1 human c20 | References$ X
SVar:X:Count$TotalCommanderCastFromCommandZone
S:Mode$ Continuous | Affected$ Human.Other+YouCtrl | AddPower$ 2 | Description$ Other Humans you control get +2/+0.
SVar:PlayMain1:TRUE
DeckHints:Type$Human
Oracle:When Jirina Kudro enters the battlefield, create a 1/1 white Human Soldier creature token for each time you've cast a commander from the command zone this game.\nOther Humans you control get +2/+0.

View File

@@ -0,0 +1,10 @@
Name:Kalamax, the Stormsire
ManaCost:1 G U R
Types:Legendary Creature Elemental Dinosaur
PT:4/4
T:Mode$ SpellCast | ValidCard$ Instant | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast your first instant spell each turn, if CARDNAME is tapped, copy that spell. You may choose new targets for the copy.
SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | IsPresent$ Card.Self+tapped | AILogic$ Always
SVar:BuffedBy:Instant
T:Mode$ SpellCopy | ValidCard$ Instant | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you copy an instant spell, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
Oracle:Whenever you cast your first instant spell each turn, if Kalamax, the Stormsire is tapped, copy that spell. You may choose new targets for the copy.\nWhenever you copy an instant spell, put a +1/+1 counter on Kalamax.

View File

@@ -0,0 +1,6 @@
Name:Back for More
ManaCost:4 B G
Types:Instant
A:SP$ ChangeZone | Cost$ 4 B G | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | SubAbility$ DBFight | StackDescription$ SpellDescription | SpellDescription$ Return target creature card from your graveyard to the battlefield. When you do, it fights up to one target creature you don't control.
SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control | SubAbility$ DBCleanup | StackDescription$ None
Oracle:Return target creature card from your graveyard to the battlefield. When you do, it fights up to one target creature you don't control. (Each deals damage equal to its power to the other.)

View File

@@ -0,0 +1,6 @@
Name:Boon of the Wish-Giver
ManaCost:4 U U
Types:Sorcery
A:SP$ Draw | Cost$ 4 U U | NumCards$ 4 | SpellDescription$ Draw four cards.
K:Cycling:1
Oracle:Draw four cards.\nCycling {1} ({1}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,7 @@
Name:Channeled Force
ManaCost:2 U R
Types:Instant
A:SP$ Draw | Cost$ 2 U R Discard<X/Card/card> | CostDesc$ As an additional cost to cast this spell, discard X cards. | NumCards$ ChosenX | ValidTgts$ Player | TgtPrompt$ Choose a player | References$ X | SubAbility$ DBDamage | SpellDescription$ Target player draws X cards. CARDNAME deals X damage to up to one target creature or planeswalker.
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ ChosenX | References$ X
SVar:X:XChoice
Oracle:As an additional cost to cast this spell, discard X cards.\nTarget player draws X cards. Channeled Force deals X damage to up to one target creature or planeswalker.

View File

@@ -0,0 +1,13 @@
Name:Chevill, Bane of Monsters
ManaCost:B G
Types:Legendary Creature Human Rogue
PT:1/3
K:Deathtouch
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | IsPresent$ Permanent.OppCtrl+counters_GE1_BOUNTY | PresentCompare$ EQ0 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your upkeep, if your opponents control no permanents with bounty counters on them, put a bounty counter on target creature or planeswalker an opponent controls.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.OppCtrl,Planeswalker.OppCtrl | TgtPrompt$ Select target creature or planeswalker an opponent controls | CounterType$ BOUNTY | CounterNum$ 1 | IsCurse$ True
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Permanent.OppCtrl+counters_GE1_BOUNTY | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever a permanent an opponent controls with a bounty counter on it dies, you gain 3 life and draw a card.
SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 3 | SubAbility$ DBDraw
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1
SVar:PlayMain1:TRUE
DeckHints:Ability$Counters
Oracle:Deathtouch\nAt the beginning of your upkeep, if your opponents control no permanents with bounty counters on them, put a bounty counter on target creature or planeswalker an opponent controls.\nWhenever a permanent an opponent controls with a bounty counter on it dies, you gain 3 life and draw card.

View File

@@ -0,0 +1,10 @@
Name:Death's Oasis
ManaCost:W B G
Types:Enchantment
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+nonToken | TriggerZones$ Battlefield | Execute$ TrigMill | TriggerDescription$ Whenever a nontoken creature you control dies, put the top two cards of your library into your graveyard. Then return a creature card with lesser converted mana cost than the creature that died from the graveyard to your hand.
SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You | SubAbility$ DBReturn
SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Creature.YouOwn+cmcLTY | References$ Y | Hidden$ True | ChangeNum$ 1
SVar:Y:TriggeredCard$CardManaCost
A:AB$ GainLife | Cost$ 1 Sac<1/CARDNAME> | LifeAmount$ X | References$ X | SpellDescription$ You gain life equal to the greatest converted mana cost among creatures you control.
SVar:X:Count$HighestCMC_Creature.YouCtrl+inZoneBattlefield
Oracle:Whenever a nontoken creature you control dies, put the top two cards of your library into your graveyard. Then return a creature card with lesser converted mana cost than the creature that died from the graveyard to your hand.\n{1}, Sacrifice Death's Oasis: You gain life equal to the greatest converted mana cost among creatures you control.

View File

@@ -0,0 +1,7 @@
Name:Dire Tactics
ManaCost:W B
Types:Instant
A:SP$ ChangeZone | Cost$ W B | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBLoseLife | StackDescription$ SpellDescription | SpellDescription$ Exile target creature. If you don't control a Human, you lose life equal to that creature's toughness.
SVar:DBLoseLife:DB$ LoseLife | ConditionPresent$ Human.YouCtrl | ConditionCompare$ EQ0 | Defined$ You | LifeAmount$ X | References$ X | StackDescription$ None
SVar:X:Targeted$CardToughness
Oracle:Exile target creature. If you don't control a Human, you lose life equal to that creature's toughness.

View File

@@ -0,0 +1,6 @@
Name:Drannith Magistrate
ManaCost:1 W
Types:Creature Human Wizard
PT:1/3
S:Mode$ CantBeCast | ValidCard$ Card | Caster$ Opponent | Origin$ Library,Graveyard,Exile | Description$ Your opponents can't cast spells from anywhere other than their hands.
Oracle:Your opponents can't cast spells from anywhere other than their hands.

View File

@@ -0,0 +1,11 @@
Name:Fiend Artisan
ManaCost:B/G B/G
Types:Creature Nightmare
PT:1/1
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ Y | AddToughness$ Y | Description$ CARDNAME gets +1/+1 for each creature card in your graveyard.
A:AB$ ChangeZone | Cost$ X BG T Sac<1/Creature.Other/another creature> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.cmcLEX | ChangeNum$ 1 | References$ X | ChangeNum$ 1 | SorcerySpeed$ True | StackDescription$ SpellDescription | SpellDescription$ Search your library for a creature card with converted mana cost X or less, put it onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery.
SVar:Y:Count$TypeInYourYard.Creature
SVar:X:Count$xPaid
AI:RemoveDeck:All
DeckHints:Ability$Graveyard
Oracle:Fiend Artisan gets +1/+1 for each creature card in your graveyard.\n{X}{B/G}, {T}, Sacrifice another creature: Search your library for a creature card with converted mana cost X or less, put it onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery.

View File

@@ -0,0 +1,8 @@
Name:Flourishing Fox
ManaCost:W
Types:Creature Fox
PT:1/1
T:Mode$ Cycled | ValidCard$ Card.Other | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cycle another card, put a +1/+1 counter on CARDNAME.
SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1
K:Cycling:1
Oracle:Whenever you cycle another card, put a +1/+1 counter on Flourishing Fox.\nCycling {1} ({1}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,6 @@
Name:Fully Grown
ManaCost:2 G
Types:Instant
A:SP$ Pump | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +3 | SubAbility$ PutCounter | SpellDescription$ Target creature gets +3/+3 until end of turn. Put a trample counter on it.
SVar:PutCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ TRAMPLE | CounterNum$ 1
Oracle:Target creature gets +3/+3 until end of turn. Put a trample counter on it.

View File

@@ -0,0 +1,11 @@
Name:General Kudro of Drannith
ManaCost:1 W B
Types:Legendary Creature Human Soldier
PT:3/3
S:Mode$ Continuous | Affected$ Human.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Humans you control get +1/+1.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Whenever CARDNAME or another Human enters the battlefield under your control, exile target card from an opponent's graveyard.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+Human+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExile | Secondary$ True | TriggerDescription$ Whenever CARDNAME or another Human enters the battlefield under your control, exile target card from an opponent's graveyard.
SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in an opponent's graveyard | ValidTgts$ Card.OppOwn
A:AB$ Destroy | Cost$ 2 Sac<2/Human> | ValidTgts$ Creature.powerGE4 | TgtPrompt$ Select target creature with power 4 or greater | SpellDescription$ Destroy target creature with power 4 or greater.
DeckHints:Type$Human
Oracle:Other Humans you control get +1/+1.\nWhenever General Kudro of Drannith or another Human enters the battlefield under your control, exile target card from an opponent's graveyard.\n{2}, Sacrifice two Humans: Destroy target creature with power 4 or greater.

View File

@@ -0,0 +1,9 @@
Name:General's Enforcer
ManaCost:W B
Types:Creature Human Soldier
PT:2/3
S:Mode$ Continuous | Affected$ Human.YouCtrl+Legendary | AddKeyword$ Indestructible | Description$ Legendary Humans you control have indestructible.
A:AB$ ChangeZone | Cost$ 2 W B | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SubAbility$ DBToken | SpellDescription$ Exile target card from a graveyard. If it was a creature card, create a 1/1 white Human Soldier creature token.
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human_soldier | TokenOwner$ You | LegacyImage$ w 1 1 human soldier iko | ConditionDefined$ Targeted | ConditionPresent$ Creature | ConditionCompare$ EQ1 | StackDescription$ If it was a creature card, create a 1/1 white Human Soldier creature token.
DeckHints:Type$Human
Oracle:Legendary Humans you control have indestructible.\n{2}{W}{B}: Exile target card from a graveyard. If it was a creature card, create a 1/1 white Human Soldier creature token.

View File

@@ -0,0 +1,8 @@
Name:Indatha Crystal
ManaCost:3
Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}.
A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
K:Cycling:2
Oracle:{T}: Add {W}, {B}, or {G}.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,8 @@
Name:Ketria Crystal
ManaCost:3
Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.
A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}.
K:Cycling:2
Oracle:{T}: Add {G}, {U}, or {R}.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,6 @@
Name:Migration Path
ManaCost:3 G
Types:Sorcery
A:SP$ ChangeZone | Cost$ 3 G | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library.
K:Cycling:2
Oracle:Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,6 @@
Name:Neutralize
ManaCost:1 U U
Types:Instant
A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell.
K:Cycling:2
Oracle:Counter target spell.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,7 @@
Name:Phase Dolphin
ManaCost:2 U
Types:Creature Elemental Whale
PT:1/4
T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, another target attacking creature can't be blocked this turn.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.attacking+Other | TgtPrompt$ Select another target attacking creature | KW$ HIDDEN Unblockable
Oracle:Whenever Phase Dolphin attacks, another target attacking creature can't be blocked this turn.

View File

@@ -1,13 +1,15 @@
Name:Primal Empathy Name:Primal Empathy
ManaCost:1 G U ManaCost:1 G U
Types:Enchantment Types:Enchantment
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigBranch | TriggerDescription$ At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionPresent$ Creature | ConditionCompare$ GE1 | ConditionPresent$ Creature | ConditionCompare$ GE1 | ConditionCheckSVar$ Z | ConditionSVarCompare$ GEY | References$ Y,Z | RememberDrawn$ True | SubAbility$ DBChoose SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | References$ X | TrueSubAbility TrigBranch2 | FalseSubAbility$ DBPutCounter
SVar:DBChoose:DB$ ChooseCard | Defined$ You | Choices$ Creature.YouCtrl | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | References$ X | SubAbility$ DBPutCounter SVar:TrigBranch2:DB$ Branch | BranchConditionSVar$ GreatPow | References$ GreatPow | TrueSubAbility$ DBDraw | FalseSubAbility$ DBPutCounter
SVar:DBPutCounter:DB$ PutCounter | Defined$ ChosenCard | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBCleanup SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True SVar:DBPutCounter:DB$ PutCounter | Choices$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1
SVar:X:Count$RememberedSize SVar:GreatPow:Count$Compare Diff GE0.1.0
SVar:X:Count$Valid Creature.YouCtrl
SVar:Y:Count$GreatestPower_Creature.YouDontCtrl SVar:Y:Count$GreatestPower_Creature.YouDontCtrl
SVar:Z:Count$GreatestPower_Creature.YouCtrl SVar:Z:Count$GreatestPower_Creature.YouCtrl
SVar:Diff:SVar$Z/Minus.Y
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control. Oracle:At the beginning of your upkeep, draw a card if you control a creature with the greatest power among creatures on the battlefield. Otherwise, put a +1/+1 counter on a creature you control.

View File

@@ -0,0 +1,7 @@
Name:Proud Wildbonder
ManaCost:2 R/G R/G
Types:Creature Human Warrior
PT:4/3
K:Trample
S:Mode$ Continuous | Affected$ Creature.YouCtrl+withTrample | AddKeyword$ You may have CARDNAME assign its combat damage as though it weren't blocked. | Description$ Creatures you control with trample have "You may have this creature assign its combat damage as though it weren't blocked."
Oracle:Trample\nCreatures you control with trample have "You may have this creature assign its combat damage as though it weren't blocked."

View File

@@ -0,0 +1,8 @@
Name:Raugrin Crystal
ManaCost:3
Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.
A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}.
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
K:Cycling:2
Oracle:{T}: Add {U}, {R}, or {W}.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,7 @@
Name:Reconnaissance Mission
ManaCost:2 U U
Types:Enchantment
T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | CombatDamage$ True | OptionalDecider$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever a creature you control deals combat damage to a player, you may draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1
K:Cycling:2
Oracle:Whenever a creature you control deals combat damage to a player, you may draw a card.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,8 @@
Name:Savai Crystal
ManaCost:3
Types:Artifact
A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}.
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}.
K:Cycling:2
Oracle:{T}: Add {R}, {W}, or {B}.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,5 @@
Name:Savai Sabertooth
ManaCost:1 W
Types:Creature Cat
PT:3/1
Oracle:

View File

@@ -0,0 +1,8 @@
Name:Shredded Sails
ManaCost:1 R
Types:Instant
A:SP$ Charm | Cost$ 1 R | CharmNum$ 1 | Choices$ DestArt,DmgFly
SVar:DestArt:DB$ Destroy | ValidTgts$ Artifact | SpellDescription$ Destroy target artifact.
SVar:DmgFly:DB$ DealDamage | ValidTgts$ Creature.withFlying | NumDmg$ 2 | SpellDescription$ CARDNAME deals 4 damage to target creature with flying.
K:Cycling:2
Oracle:Choose one —\n• Destroy target artifact.\n• Shredded Sails deals 4 damage to target creature with flying.\nCycling {2} ({2}, Discard this card: Draw a card.)

View File

@@ -0,0 +1,9 @@
Name:Skull Prophet
ManaCost:B G
Types:Creature Human Druid
PT:3/1
A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}.
A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}.
A:AB$ Mill | Cost$ T | Defined$ You | NumCards$ 2 | SpellDescription$ Put the top two cards of your library into your graveyard.
DeckHas:Ability$Graveyard
Oracle:{T}: Add {B} or {G}.\n{T}: Put the top two cards of your library into your graveyard.

View File

@@ -0,0 +1,111 @@
[metadata]
Code=IKO
Date=2020-04-16
Name=Ikoria: Lair of Behemoths
Type=Expansion
[cards]
3 C Mysterious Egg
11 R Drannith Magistrate
13 U Flourishing Fox
16 U Huntmaster Liger
24 R Mythos of Snapdax
25 C Pacifism
29 C Savai Sabertooth
34 U Stormwild Capridor
36 U Valiant Rescuer
38 U "Knock Blocked"
41 U Archipelagore
43 U Boon of the Wish-Giver
47 C Dreamtail Heron
49 C Essence Scatter
59 U Neutralize
61 U "What's Kraken"
62 C Phase Dolphin
63 U Pollywog Symbiote
64 U Pouncing Shoreshark
65 U Reconnaissance Mission
67 R Shark Typhoon
69 C Thieving Otter
70 R Voracious Greatshark
71 C Wingfold Pteron
73 U Bastion of Remembrance
75 C Blood Curdle
78 U Call of the Death-Dweller
79 C Cavern Whisperer
80 U Chittering Harvester
84 R Dirge Bat
88 R "Odd the Evens"
89 C Gloom Pangolin
90 U Grimdancer
93 U Insatiable Hemophage
97 R Mythos of Nethroi
101 U "Learning Social Distancing"
104 U Void Beckoner
106 U Zagoth Mamba
111 U "Round 2: Fight!"
112 C Cloudpiercer
113 C Drannith Stinger
114 R Everquill Phoenix
118 U Footfall Crater
125 M Lukka, Coppercoat Outcast
127 R Mythos of Vadrok
133 U "Recycling Monitor"
136 C Shredded Sails
141 R Yidaro, Wandering Monster
144 U Auspicious Starrix
145 U "Breach the Barrier"
146 C Bristling Boar
147 U "Trump Shard"
148 R Colossification
152 C Fertilid
154 C Fully Grown
155 R Gemrazer
156 U Glowstone Recluse
162 R Kogla, the Titan Ape
164 U Migration Path
165 C "Spring Cleaner"
166 U Monstruous Step
167 C Mosscoat Goriak
168 R "Mythos of Brokkos"
174 U Titanoth Rex
175 M Vivien, Monsters' Advocate
176 C "Harmless Houseplant"
177 U Back for More
178 U "BLARGbeast"
179 M Brokkos, Apex of Forever
180 U Channeled Force
181 M Chevill, Bane of Monsters
182 R Death's Oasis
183 U Dire Tactics
187 M General Kudro of Drannith
188 U General's Enforcer
190 M Illuna, Apex of Wishes
193 R Labyrinth Raptor
197 M Nethroi, Apex of Death
200 U Primal Empathy
201 R "Big Boy Forest Crusher"
203 M Rielle, the Everwise
206 U Skull Prophet
209 M Snapdax, Apex of the Hunt
211 U Sprite Dragon
213 U Trumpeting Gnarr
214 M Vadrok, Apex of Thunder
220 M Fiend Artisan
221 R Gyruda, Doom of Depths
227 R Lutri, the Spellchaser
228 R "Obosh, With The Leggies"
229 U Proud Wildbonder
231 R Umori, the Collector
234 R Crystalline Giant
235 U Indatha Crystal
236 U Ketria Crystal
238 U Raugrin Crystal
239 U Savai Crystal
242 U Zagoth Crystal
275 M Zilortha, Strength Incarnate
316 M Luminous Broodmoth
354 R Keruga, the Macrosage
[tokens]
w_1_1_human_soldier

View File

@@ -20,6 +20,8 @@ w_2_2_cat
w_0_1_goat w_0_1_goat
w_1_1_spirit_flying w_1_1_spirit_flying
w_1_1_spirit_flying w_1_1_spirit_flying
w_4_4_angel_flying
w_4_4_angel_flying
w_1_1_soldier w_1_1_soldier
u_1_1_faerie_spy_flying_haste_draw u_1_1_faerie_spy_flying_haste_draw
u_8_8_octopus u_8_8_octopus
@@ -45,6 +47,7 @@ g_1_1_squirrel
g_6_6_wurm g_6_6_wurm
gw_x_x_elemental_total_creatures gw_x_x_elemental_total_creatures
gw_x_x_elemental_total_creatures gw_x_x_elemental_total_creatures
wubrg_4_4_dragon
c_a_clue_draw c_a_clue_draw
c_a_clue_draw c_a_clue_draw
c_x_x_a_construct c_x_x_a_construct

View File

@@ -6,6 +6,7 @@ public class GuiBase {
private static IGuiBase guiInterface; private static IGuiBase guiInterface;
private static boolean propertyConfig = true; private static boolean propertyConfig = true;
private static boolean networkplay = false; private static boolean networkplay = false;
private static boolean isAndroidport = false;
public static IGuiBase getInterface() { public static IGuiBase getInterface() {
return guiInterface; return guiInterface;
@@ -13,9 +14,9 @@ public class GuiBase {
public static void setInterface(IGuiBase i0) { public static void setInterface(IGuiBase i0) {
guiInterface = i0; guiInterface = i0;
} }
public static void enablePropertyConfig(boolean value) { public static void enablePropertyConfig(boolean value) { propertyConfig = value; }
propertyConfig = value; public static void setIsAndroid(boolean value) { isAndroidport = value; }
} public static boolean isAndroid() { return isAndroidport; }
public static boolean isNetworkplay() { public static boolean isNetworkplay() {
return networkplay; return networkplay;
} }

View File

@@ -0,0 +1,55 @@
package forge.net;
import io.netty.handler.codec.serialization.ClassResolver;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.StreamCorruptedException;
public class CObjectInputStream extends ObjectInputStream {
private final ClassResolver classResolver;
CObjectInputStream(InputStream in, ClassResolver classResolver) throws IOException {
super(in);
this.classResolver = classResolver;
}
protected void readStreamHeader() throws IOException {
int version = this.readByte() & 255;
if (version != 5) {
throw new StreamCorruptedException("Unsupported version: " + version);
}
}
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
int type = this.read();
if (type < 0) {
throw new EOFException();
} else {
switch(type) {
case 0:
return super.readClassDescriptor();
case 1:
String className = this.readUTF();
Class<?> clazz = this.classResolver.resolve(className);
return ObjectStreamClass.lookupAny(clazz);
default:
throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
}
}
}
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class clazz;
try {
clazz = this.classResolver.resolve(desc.getName());
} catch (ClassNotFoundException var4) {
clazz = super.resolveClass(desc);
}
return clazz;
}
}

View File

@@ -0,0 +1,31 @@
package forge.net;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
public class CObjectOutputStream extends ObjectOutputStream {
static final int TYPE_FAT_DESCRIPTOR = 0;
static final int TYPE_THIN_DESCRIPTOR = 1;
CObjectOutputStream(OutputStream out) throws IOException {
super(out);
}
protected void writeStreamHeader() throws IOException {
this.writeByte(5);
}
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
Class<?> clazz = desc.forClass();
if (!clazz.isPrimitive() && !clazz.isArray() && !clazz.isInterface() && desc.getSerialVersionUID() != 0L) {
this.write(1);
this.writeUTF(desc.getName());
} else {
this.write(0);
super.writeClassDescriptor(desc);
}
}
}

View File

@@ -0,0 +1,46 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.serialization.ClassResolver;
import java.io.ObjectInputStream;
import java.io.StreamCorruptedException;
public class CompatibleObjectDecoder extends LengthFieldBasedFrameDecoder {
private final ClassResolver classResolver;
public CompatibleObjectDecoder(ClassResolver classResolver) {
this(1048576, classResolver);
}
public CompatibleObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
super(maxObjectSize, 0, 4, 0, 4);
this.classResolver = classResolver;
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf)super.decode(ctx, in);
if (frame == null) {
return null;
} else {
ObjectInputStream ois = GuiBase.hasPropertyConfig() ?
new ObjectInputStream(new ByteBufInputStream(frame, true)):
new CObjectInputStream(new ByteBufInputStream(frame, true),this.classResolver);
Object var5 = null;
try {
var5 = ois.readObject();
} catch (StreamCorruptedException e) {
e.printStackTrace();
} finally {
ois.close();
}
return var5;
}
}
}

View File

@@ -0,0 +1,37 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class CompatibleObjectEncoder extends MessageToByteEncoder<Serializable> {
private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
@Override
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {
int startIdx = out.writerIndex();
ByteBufOutputStream bout = new ByteBufOutputStream(out);
ObjectOutputStream oout = null;
try {
bout.write(LENGTH_PLACEHOLDER);
oout = GuiBase.hasPropertyConfig() ? new ObjectOutputStream(bout) : new CObjectOutputStream(bout);
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
int endIdx = out.writerIndex();
out.setInt(startIdx, endIdx - startIdx - 4);
}
}

View File

@@ -1,58 +0,0 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.serialization.ClassResolver;
import org.mapdb.elsa.ElsaObjectInputStream;
import java.io.ObjectInputStream;
public class CustomObjectDecoder extends LengthFieldBasedFrameDecoder {
private final ClassResolver classResolver;
public CustomObjectDecoder(ClassResolver classResolver) {
this(1048576, classResolver);
}
public CustomObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
super(maxObjectSize, 0, 4, 0, 4);
this.classResolver = classResolver;
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
} else {
if (GuiBase.hasPropertyConfig()){
ElsaObjectInputStream ois = new ElsaObjectInputStream(new ByteBufInputStream(frame, true));
Object var5;
try {
var5 = ois.readObject();
} finally {
ois.close();
}
return var5;
}
else {
ObjectInputStream ois = new ObjectInputStream(new ByteBufInputStream(frame, true));
Object var5;
try {
var5 = ois.readObject();
} finally {
ois.close();
}
return var5;
}
}
}
public static int maxObjectsize = 10000000; //10megabyte???
}

View File

@@ -1,56 +0,0 @@
package forge.net;
import forge.GuiBase;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.mapdb.elsa.ElsaObjectOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class CustomObjectEncoder extends MessageToByteEncoder<Serializable> {
private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
public CustomObjectEncoder() {
}
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {
int startIdx = out.writerIndex();
ByteBufOutputStream bout = new ByteBufOutputStream(out);
if (GuiBase.hasPropertyConfig()){
ElsaObjectOutputStream oout = null;
try {
bout.write(LENGTH_PLACEHOLDER);
oout = new ElsaObjectOutputStream(bout);
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
} else {
ObjectOutputStream oout = null;
try {
bout.write(LENGTH_PLACEHOLDER);
oout = new ObjectOutputStream(bout);
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
}
int endIdx = out.writerIndex();
out.setInt(startIdx, endIdx - startIdx - 4);
}
}

View File

@@ -1,8 +1,10 @@
package forge.net; package forge.net;
import forge.FThreads; import forge.FThreads;
import forge.assets.FSkinProp;
import forge.net.event.GuiGameEvent; import forge.net.event.GuiGameEvent;
import forge.net.event.ReplyEvent; import forge.net.event.ReplyEvent;
import forge.util.gui.SOptionPane;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -25,6 +27,7 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
@Override @Override
public final void channelRead(final ChannelHandlerContext ctx, final Object msg) { public final void channelRead(final ChannelHandlerContext ctx, final Object msg) {
final String[] catchedError = {""};
System.out.println("Received: " + msg); System.out.println("Received: " + msg);
if (msg instanceof ReplyEvent) { if (msg instanceof ReplyEvent) {
final ReplyEvent event = (ReplyEvent) msg; final ReplyEvent event = (ReplyEvent) msg;
@@ -36,7 +39,9 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
final Method method = protocolMethod.getMethod(); final Method method = protocolMethod.getMethod();
if (method == null) { if (method == null) {
throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); //throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
catchedError[0] += String.format("IllegalStateException: Method %s not found (GameProtocolHandler.java Line 43)\n", protocolMethod.name());
System.err.println(String.format("Method %s not found", protocolMethod.name()));
} }
final Object[] args = event.getObjects(); final Object[] args = event.getObjects();
@@ -56,7 +61,9 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
} catch (final IllegalAccessException | IllegalArgumentException e) { } catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length)); System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) { } catch (final InvocationTargetException e) {
throw new RuntimeException(e.getTargetException()); //throw new RuntimeException(e.getTargetException());
catchedError[0] += (String.format("RuntimeException: %s (GameProtocolHandler.java Line 65)\n", e.getTargetException().toString()));
System.err.println(e.getTargetException().toString());
} }
} else { } else {
Serializable reply = null; Serializable reply = null;
@@ -70,8 +77,11 @@ public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapte
} }
} catch (final IllegalAccessException | IllegalArgumentException e) { } catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length)); System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) { } catch (final NullPointerException | InvocationTargetException e) {
throw new RuntimeException(e.getTargetException()); //throw new RuntimeException(e.getTargetException());
catchedError[0] += e.toString();
SOptionPane.showMessageDialog(catchedError[0], "Error", FSkinProp.ICO_WARNING);
System.err.println(e.toString());
} }
getRemote(ctx).send(new ReplyEvent(event.getId(), reply)); getRemote(ctx).send(new ReplyEvent(event.getId(), reply));
} }

View File

@@ -1,6 +1,7 @@
package forge.net; package forge.net;
import forge.match.LobbySlotType; import forge.match.LobbySlotType;
import forge.properties.ForgeConstants;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import forge.GuiBase; import forge.GuiBase;
@@ -179,7 +180,8 @@ public class NetConnectUtil {
client.connect(hostname, port); client.connect(hostname, port);
} }
catch (Exception ex) { catch (Exception ex) {
return null; //return a message to close the connection so we will not crash...
return new ChatMessage(null, ForgeConstants.CLOSE_CONN_COMMAND);
} }
return new ChatMessage(null, Localizer.getInstance().getMessage("lblConnectedIPPort", hostname, String.valueOf(port))); return new ChatMessage(null, Localizer.getInstance().getMessage("lblConnectedIPPort", hostname, String.valueOf(port)));

View File

@@ -1,7 +1,6 @@
package forge.net; package forge.net;
import com.google.common.base.Function; import com.google.common.base.Function;
import forge.GuiBase;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.game.GameEntityView; import forge.game.GameEntityView;
@@ -18,12 +17,9 @@ import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import forge.util.ReflectionUtil; import forge.util.ReflectionUtil;
import org.apache.commons.lang3.SerializationUtils;
import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -159,24 +155,15 @@ public enum ProtocolMethod {
} }
public void checkArgs(final Object[] args) { public void checkArgs(final Object[] args) {
if (GuiBase.hasPropertyConfig())
return; //uses custom serializer for Android 8+..
for (int iArg = 0; iArg < args.length; iArg++) { for (int iArg = 0; iArg < args.length; iArg++) {
Object arg = null; final Object arg = args[iArg];
Class<?> type = null; final Class<?> type = this.args[iArg];
try { if (!ReflectionUtil.isInstance(arg, type)) {
arg = args[iArg]; //throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
if (this.args.length > iArg) System.err.println(String.format("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java Line 163)", name(), iArg, arg.getClass().getName(), type.getName()));
type = this.args[iArg];
} }
catch (ArrayIndexOutOfBoundsException ex){ ex.printStackTrace(); } //this should be handled via decoder or it will process them twice
catch(ConcurrentModificationException ex) { ex.printStackTrace(); } /*if (arg != null) {
if (arg != null)
if (type != null)
if (!ReflectionUtil.isInstance(arg, type)) {
throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
}
if (arg != null) {
// attempt to Serialize each argument, this will throw an exception if it can't. // attempt to Serialize each argument, this will throw an exception if it can't.
try { try {
byte[] serialized = SerializationUtils.serialize((Serializable) arg); byte[] serialized = SerializationUtils.serialize((Serializable) arg);
@@ -189,7 +176,7 @@ public enum ProtocolMethod {
// can't seem to avoid this from periodically happening // can't seem to avoid this from periodically happening
ex.printStackTrace(); ex.printStackTrace();
} }
} }*/
} }
} }
@@ -199,7 +186,8 @@ public enum ProtocolMethod {
return; return;
} }
if (!ReflectionUtil.isInstance(value, returnType)) { if (!ReflectionUtil.isInstance(value, returnType)) {
throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName())); //throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName()));
System.err.println(String.format("IllegalStateException: Protocol method %s: illegal return object type %s returned by client, expected %s (ProtocolMethod.java Line 190)", name(), value.getClass().getName(), getReturnType().getName()));
} }
} }
} }

View File

@@ -1,5 +1,7 @@
package forge.net.client; package forge.net.client;
import forge.net.CompatibleObjectDecoder;
import forge.net.CompatibleObjectEncoder;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
@@ -26,8 +28,6 @@ import forge.net.event.IdentifiableNetEvent;
import forge.net.event.LobbyUpdateEvent; import forge.net.event.LobbyUpdateEvent;
import forge.net.event.MessageEvent; import forge.net.event.MessageEvent;
import forge.net.event.NetEvent; import forge.net.event.NetEvent;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
public class FGameClient implements IToServer { public class FGameClient implements IToServer {
@@ -58,8 +58,8 @@ public class FGameClient implements IToServer {
public void initChannel(final SocketChannel ch) throws Exception { public void initChannel(final SocketChannel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline(); final ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast( pipeline.addLast(
new ObjectEncoder(), new CompatibleObjectEncoder(),
new ObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)),
new MessageHandler(), new MessageHandler(),
new LobbyUpdateHandler(), new LobbyUpdateHandler(),
new GameClientHandler(FGameClient.this)); new GameClientHandler(FGameClient.this));
@@ -86,7 +86,8 @@ public class FGameClient implements IToServer {
} }
public void close() { public void close() {
channel.close(); if (channel != null)
channel.close();
} }
@Override @Override

View File

@@ -6,6 +6,8 @@ import forge.interfaces.IGuiGame;
import forge.interfaces.ILobbyListener; import forge.interfaces.ILobbyListener;
import forge.match.LobbySlot; import forge.match.LobbySlot;
import forge.match.LobbySlotType; import forge.match.LobbySlotType;
import forge.net.CompatibleObjectDecoder;
import forge.net.CompatibleObjectEncoder;
import forge.net.event.LobbyUpdateEvent; import forge.net.event.LobbyUpdateEvent;
import forge.net.event.LoginEvent; import forge.net.event.LoginEvent;
import forge.net.event.LogoutEvent; import forge.net.event.LogoutEvent;
@@ -24,8 +26,6 @@ import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; import io.netty.handler.logging.LoggingHandler;
@@ -99,8 +99,8 @@ public final class FServerManager {
public final void initChannel(final SocketChannel ch) throws Exception { public final void initChannel(final SocketChannel ch) throws Exception {
final ChannelPipeline p = ch.pipeline(); final ChannelPipeline p = ch.pipeline();
p.addLast( p.addLast(
new ObjectEncoder(), new CompatibleObjectEncoder(),
new ObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)),
new MessageHandler(), new MessageHandler(),
new RegisterClientHandler(), new RegisterClientHandler(),
new LobbyInputHandler(), new LobbyInputHandler(),
@@ -180,6 +180,15 @@ public final class FServerManager {
this.localLobby = lobby; this.localLobby = lobby;
} }
public void unsetReady() {
if (this.localLobby != null) {
if (this.localLobby.getSlot(0) != null) {
this.localLobby.getSlot(0).setIsReady(false);
updateLobbyState();
}
}
}
public boolean isMatchActive() { public boolean isMatchActive() {
return this.localLobby != null && this.localLobby.isMatchActive(); return this.localLobby != null && this.localLobby.isMatchActive();
} }

View File

@@ -237,6 +237,7 @@ public final class ForgeConstants {
public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences"; public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences";
public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences"; public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences";
public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences"; public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences";
public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<";
// data that has defaults in the program dir but overrides/additions in the user dir // data that has defaults in the program dir but overrides/additions in the user dir
private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR; private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR;

View File

@@ -139,7 +139,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
UI_ENABLE_PRELOAD_EXTENDED_ART("false"), UI_ENABLE_PRELOAD_EXTENDED_ART("false"),
UI_ENABLE_BORDER_MASKING("false"), UI_ENABLE_BORDER_MASKING("false"),
UI_SHOW_FPS("false"), UI_SHOW_FPS("false"),
UI_USE_ELSA("false"), UI_NETPLAY_COMPAT("false"),
UI_LOAD_UNKNOWN_CARDS("true"), UI_LOAD_UNKNOWN_CARDS("true"),
UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED ("Never"), UI_ALLOW_ORDER_GRAVEYARD_WHEN_NEEDED ("Never"),
UI_DEFAULT_FONT_SIZE("12"), UI_DEFAULT_FONT_SIZE("12"),

View File

@@ -22,6 +22,7 @@ import forge.game.event.GameEventCardSacrificed;
import forge.game.event.GameEventCardTapped; import forge.game.event.GameEventCardTapped;
import forge.game.event.GameEventFlipCoin; import forge.game.event.GameEventFlipCoin;
import forge.game.event.GameEventGameOutcome; import forge.game.event.GameEventGameOutcome;
import forge.game.event.GameEventGameStarted;
import forge.game.event.GameEventLandPlayed; import forge.game.event.GameEventLandPlayed;
import forge.game.event.GameEventManaBurn; import forge.game.event.GameEventManaBurn;
import forge.game.event.GameEventPlayerLivesChanged; import forge.game.event.GameEventPlayerLivesChanged;
@@ -47,6 +48,11 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> imp
this.player = lobbyPlayer; this.player = lobbyPlayer;
} }
@Override
public SoundEffectType visit(GameEventGameStarted event) {
return SoundEffectType.StartDuel;
}
@Override @Override
public SoundEffectType visit(final GameEventCardDamaged event) { return SoundEffectType.Damage; } public SoundEffectType visit(final GameEventCardDamaged event) { return SoundEffectType.Damage; }
@Override @Override

View File

@@ -83,6 +83,7 @@ public enum SoundEffectType {
ScriptedEffect("", false), // Plays the effect defined by SVar:SoundEffect ScriptedEffect("", false), // Plays the effect defined by SVar:SoundEffect
Shuffle("shuffle.wav", false), Shuffle("shuffle.wav", false),
Sorcery("sorcery.wav", false), Sorcery("sorcery.wav", false),
StartDuel("start_duel.wav",false),
Tap("tap.wav", false), Tap("tap.wav", false),
Token("token.wav", true), Token("token.wav", true),
Untap("untap.wav", true), Untap("untap.wav", true),