diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form index 8a9d385eae..6f93568ea0 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.form @@ -59,6 +59,10 @@ + + + + @@ -133,6 +137,8 @@ + + @@ -389,5 +395,12 @@ + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java index 2e41a98513..95158581c3 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java @@ -79,6 +79,7 @@ public class NewTableDialog extends MageDialog { this.spnNumWins.setModel(new SpinnerNumberModel(1, 1, 5, 1)); this.spnFreeMulligans.setModel(new SpinnerNumberModel(0, 0, 5, 1)); this.spnQuitRatio.setModel(new SpinnerNumberModel(100, 0, 100, 5)); + this.spnEdhPowerLevel.setModel(new SpinnerNumberModel(100, 0, 100, 5)); MageFrame.getUI().addButton(MageComponents.NEW_TABLE_OK_BUTTON, btnOK); } @@ -125,7 +126,9 @@ public class NewTableDialog extends MageDialog { btnOK = new javax.swing.JButton(); btnCancel = new javax.swing.JButton(); lblQuitRatio = new javax.swing.JLabel(); + lblEdhPowerLevel = new javax.swing.JLabel(); spnQuitRatio = new javax.swing.JSpinner(); + spnEdhPowerLevel = new javax.swing.JSpinner(); setTitle("New Table"); @@ -214,8 +217,10 @@ public class NewTableDialog extends MageDialog { }); lblQuitRatio.setText("Allowed quit %"); + lblEdhPowerLevel.setText("EDH power level"); spnQuitRatio.setToolTipText("Players with quit % more than this value can't join this table"); + spnEdhPowerLevel.setToolTipText("Players with decks with a higher power level can't join this table"); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); @@ -256,7 +261,10 @@ public class NewTableDialog extends MageDialog { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lblQuitRatio) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblEdhPowerLevel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spnEdhPowerLevel, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -314,7 +322,10 @@ public class NewTableDialog extends MageDialog { .addComponent(lbDeckType) .addComponent(lblQuitRatio) .addComponent(chkRated) - .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lblEdhPowerLevel) + .addComponent(chkRated) + .addComponent(spnEdhPowerLevel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) @@ -395,6 +406,7 @@ public class NewTableDialog extends MageDialog { options.setFreeMulligans((Integer) this.spnFreeMulligans.getValue()); options.setPassword(this.txtPassword.getText()); options.setQuitRatio((Integer) this.spnQuitRatio.getValue()); + options.setEdhPowerLevel((Integer) this.spnEdhPowerLevel.getValue()); if (!checkMatchOptions(options)) { return; } @@ -658,6 +670,7 @@ public class NewTableDialog extends MageDialog { } this.spnQuitRatio.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_QUIT_RATIO, "100"))); + this.spnEdhPowerLevel.setValue(0); } /** @@ -721,6 +734,7 @@ public class NewTableDialog extends MageDialog { private javax.swing.JLabel lblNumWins; private javax.swing.JLabel lblPassword; private javax.swing.JLabel lblQuitRatio; + private javax.swing.JLabel lblEdhPowerLevel; private javax.swing.JLabel lblRange; private javax.swing.JLabel lblSkillLevel; private mage.client.table.NewPlayerPanel player1Panel; @@ -729,6 +743,7 @@ public class NewTableDialog extends MageDialog { private javax.swing.JSpinner spnNumPlayers; private javax.swing.JSpinner spnNumWins; private javax.swing.JSpinner spnQuitRatio; + private javax.swing.JSpinner spnEdhPowerLevel; private javax.swing.JTextField txtName; private javax.swing.JTextField txtPassword; // End of variables declaration//GEN-END:variables diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java index 6fef4d52ed..11fa3fbc50 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Commander.java @@ -204,4 +204,274 @@ public class Commander extends Constructed { || cardColor.isWhite() && !commander.isWhite()); } + @Override + public int getEdhPowerLevel(Deck deck) { + if (deck == null) { + return 0; + } + + int edhPowerLevel = 0; + for (Card card : deck.getCards()) { + + int thisMaxPower = 0; + + // Examine rules to work out most egregious functions in edh + boolean anyNumberOfTarget = false; + boolean buyback = false; + boolean cascade = false; + boolean copy = false; + boolean exile = false; + boolean exileAll = false; + boolean counter = false; + boolean destroy = false; + boolean destroyAll = false; + boolean each = false; + boolean exalted = false; + boolean drawCards = false; + boolean extraTurns = false; + boolean gainControl = false; + boolean infect = false; + boolean mayCastForFree = false; + boolean overload = false; + boolean persist = false; + boolean proliferate = false; + boolean retrace = false; + boolean sacrifice = false; + boolean skip = false; + boolean sliver = false; + boolean tutor = false; + boolean undying = false; + boolean wheneverEnters = false; + boolean youControlTarget = false; + + for (String str : card.getRules()) { + String s = str.toLowerCase(); + anyNumberOfTarget |= s.contains("any number"); + buyback |= s.contains("buyback"); + cascade |= s.contains("cascade"); + copy |= s.contains("copy"); + counter |= s.contains("counter") && s.contains("target"); + destroy |= s.contains("destroy"); + destroyAll |= s.contains("destroy all"); + drawCards |= s.contains("draw cards"); + each |= s.contains("each"); + exalted |= s.contains("exalted"); + exile |= s.contains("exile"); + exileAll |= s.contains("exile") && s.contains(" all "); + extraTurns |= s.contains("extra turn"); + gainControl |= s.contains("gain control"); + infect |= s.contains("infect"); + mayCastForFree |= s.contains("may cast") && s.contains("without paying"); + overload |= s.contains("overload"); + persist |= s.contains("persist"); + proliferate |= s.contains("proliferate"); + retrace |= s.contains("retrace"); + sacrifice |= s.contains("sacrifice"); + skip |= s.contains("skip") && s.contains("each"); + sliver |= s.contains("sliver"); + tutor |= s.contains("search your library"); + undying |= s.contains("undying"); + wheneverEnters |= s.contains("when") && s.contains("another") && s.contains("enters"); + youControlTarget |= s.contains("you control target"); + } + + if (extraTurns) { + thisMaxPower = Math.max(thisMaxPower, 7); + } + if (buyback) { + thisMaxPower = Math.max(thisMaxPower, 6); + } + if (tutor) { + thisMaxPower = Math.max(thisMaxPower, 6); + } + if (infect) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (overload) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + if (cascade) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (each) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (exileAll) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (gainControl) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (mayCastForFree) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (proliferate) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (skip) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (wheneverEnters) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (youControlTarget) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + if (anyNumberOfTarget) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (destroyAll) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (undying) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (persist) { + thisMaxPower = Math.max(thisMaxPower, 3); + } + if (exile) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (sliver) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (sacrifice) { + thisMaxPower = Math.max(thisMaxPower, 2); + } + if (copy) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (counter) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (destroy) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (drawCards) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (exalted) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + if (retrace) { + thisMaxPower = Math.max(thisMaxPower, 1); + } + + // Planeswalkers + if (card.getCardType().contains(CardType.PLANESWALKER)) { + if (card.getName().toLowerCase().equals("jace, the mind sculptor")) { + thisMaxPower = Math.max(thisMaxPower, 5); + } + thisMaxPower = Math.max(thisMaxPower, 3); + } + + if (card.getCardType().contains(CardType.LAND)) { + thisMaxPower = 0; + } + + // Banned in french + String cn = card.getName().toLowerCase(); + if (cn.equals("ancient tomb") || cn.equals("armageddon") + || cn.equals("aura shards") || cn.equals("back to basics") + || cn.equals("bane of progress") || cn.equals("basalt monolith") + || cn.equals("blightsteel collossus") || cn.equals("cabal coffers") + || cn.equals("craterhoof behemoth") || cn.equals("deepglow skate") + || cn.equals("dig through time") || cn.equals("entomb") + || cn.equals("food chain") || cn.equals("gaea's cradle") + || cn.equals("grim monolith") || cn.equals("hermit druid") + || cn.equals("humility") || cn.equals("imperial seal") + || cn.equals("karakas") || cn.equals("living death") + || cn.equals("loyal retainers") || cn.equals("mana crypt") + || cn.equals("mana drain") || cn.equals("mana vault") + || cn.equals("necrotic ooze") || cn.equals("oath of druids") + || cn.equals("protean hulk") || cn.equals("ravages of war") + || cn.equals("reclamation sage") || cn.equals("sensei's divning top") + || cn.equals("sol ring") || cn.equals("spore frog") + || cn.equals("strip mine") || cn.equals("the tabernacle at pendrell vale") + || cn.equals("tinker") || cn.equals("tolarian academy") + || cn.equals("winter orb") || cn.equals("treasure cruise")) { + thisMaxPower = Math.max(thisMaxPower, 4); + } + + // Parts of infinite combos + if (cn.equals("animate artifact") || cn.equals("archaeomancer") + || cn.equals("ashnod's altar") || cn.equals("azami, lady of scrolls") + || cn.equals("basalt monolith") || cn.equals("brago, king eternal") + || cn.equals("candelabra of tawnos") || cn.equals("cephalid aristocrat") + || cn.equals("cephalid illusionist") || cn.equals("changeling berserker") + || cn.equals("cinderhaze wretch") || cn.equals("cryptic gateway") + || cn.equals("deadeye navigator") || cn.equals("derevi, empyrial tactician") + || cn.equals("doubling season") || cn.equals("dross scorpion") + || cn.equals("earthcraft") || cn.equals("erratic portal") + || cn.equals("enter the infinite") || cn.equals("omniscience") + || cn.equals("exquisite blood") || cn.equals("future sight") + || cn.equals("grave titan") || cn.equals("great whale") + || cn.equals("grim monolith") || cn.equals("gush") + || cn.equals("hellkite charger") || cn.equals("intruder alarm") + || cn.equals("iona, shield of emeria") + || cn.equals("karn, silver golem") || cn.equals("kiki-jiki, mirror breaker") + || cn.equals("krark-clan ironworks") || cn.equals("krenko, mob boss") + || cn.equals("krosan restorer") || cn.equals("laboratory maniac") + || cn.equals("leovold, emissary of trest") + || cn.equals("leonin relic-warder") || cn.equals("leyline of the void") + || cn.equals("memnarch") || cn.equals("memnarch") + || cn.equals("meren of clan nel toth") || cn.equals("mikaeus, the unhallowed") + || cn.equals("mindcrank") || cn.equals("mindslaver") + || cn.equals("minion reflector") || cn.equals("mycosynth lattice") + || cn.equals("myr turbine") || cn.equals("narset, enlightened master") + || cn.equals("nekusar, the mindrazer") || cn.equals("norin the wary") + || cn.equals("opalescence") || cn.equals("ornithopter") + || cn.equals("planar portal") || cn.equals("power artifact") + || cn.equals("rings of brighthearth") || cn.equals("rite of replication") + || cn.equals("sanguine bond") || cn.equals("sensei's divining top") + || cn.equals("splinter twin") || cn.equals("stony silence") + || cn.equals("storm cauldron") || cn.equals("teferi's puzzle box") + || cn.equals("teferi, mage of zhalfir") || cn.equals("teferi, mage of zhalfir") + || cn.equals("tezzeret the seeker") || cn.equals("time stretch") + || cn.equals("time warp") || cn.equals("training grounds") + || cn.equals("triskelavus") || cn.equals("triskelion") + || cn.equals("turnabout") || cn.equals("umbral mantle") + || cn.equals("uyo, silent prophet") || cn.equals("voltaic key") + || cn.equals("workhorse") || cn.equals("worldgorger dragon") + || cn.equals("worthy cause") || cn.equals("yawgmoth's will") + || cn.equals("zealous conscripts")) { + thisMaxPower = Math.max(thisMaxPower, 6); + } + System.out.println(thisMaxPower + "Card:" + cn + " " + thisMaxPower); + + edhPowerLevel += thisMaxPower; + } + + for (Card commander : deck.getSideboard()) { + int thisMaxPower = 0; + String cn = commander.getName().toLowerCase(); + // Least fun commanders + if (cn.equals("memnarch") + || cn.equals("derevi, empyrial tactician") + || cn.equals("narset, enlightened master") + || cn.equals("nekusar, the mindrazer") + || cn.equals("norin the wary")) { + thisMaxPower = Math.max(thisMaxPower, 15); + } + + // Next least fun commanders + if (cn.equals("meren of clan nel toth") + || cn.equals("teferi, mage of zhalfir") + || cn.equals("azami, lady of scrolls") + || cn.equals("brago, king eternal") + || cn.equals("mikaeus the unhallowed") + || cn.equals("memnarch")) { + thisMaxPower = Math.max(thisMaxPower, 10); + } + + System.out.println(thisMaxPower + "Card:" + cn + " bad commander" + thisMaxPower); + edhPowerLevel += thisMaxPower; + } + + edhPowerLevel = (int) Math.round(edhPowerLevel / 2.5); + if (edhPowerLevel > 100) { + edhPowerLevel = 100; + } + return edhPowerLevel; + } } diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index 2162af3c6b..6b1704182f 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -297,6 +297,19 @@ public class TableController { user.showUserMessage("Join Table", message); return false; } + + // Check power level for table (currently only used for EDH/Commander table) + int edhPowerLevel = table.getMatch().getOptions().getEdhPowerLevel(); + if (edhPowerLevel > 0 && table.getValidator().getName().toLowerCase().equals("commander")) { + int deckEdhPowerLevel = table.getValidator().getEdhPowerLevel(deck); + if (deckEdhPowerLevel > edhPowerLevel) { + String message = new StringBuilder("Your deck appears to be too powerful for this table.\n\nReduce the number of extra turn cards, infect, counters, fogs, reconsider your commander. ") + .append("\nThe table requirement has a maximum power level of ").append(edhPowerLevel).append (" whilst your deck has a calculated power level of ") + .append(deckEdhPowerLevel).toString(); + user.showUserMessage("Join Table", message); + return false; + } + } Player player = createPlayer(name, seat.getPlayerType(), skill); if (player == null) { diff --git a/Mage/src/main/java/mage/cards/decks/DeckValidator.java b/Mage/src/main/java/mage/cards/decks/DeckValidator.java index 383e1ad8a0..bc73bc61d9 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckValidator.java +++ b/Mage/src/main/java/mage/cards/decks/DeckValidator.java @@ -67,4 +67,8 @@ public abstract class DeckValidator implements Serializable { } } } + + public int getEdhPowerLevel(Deck deck) { + return 0; + } } diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java index 37901b91be..3bcd909ab8 100644 --- a/Mage/src/main/java/mage/game/match/MatchOptions.java +++ b/Mage/src/main/java/mage/game/match/MatchOptions.java @@ -58,6 +58,7 @@ public class MatchOptions implements Serializable { protected SkillLevel skillLevel; protected boolean rollbackTurnsAllowed; protected int quitRatio; + protected int edhPowerLevel; protected boolean rated; protected int numSeatsForMatch; @@ -208,6 +209,14 @@ public class MatchOptions implements Serializable { public void setQuitRatio(int quitRatio) { this.quitRatio = quitRatio; } + + public int getEdhPowerLevel() { + return edhPowerLevel; + } + + public void setEdhPowerLevel(int edhPowerLevel) { + this.edhPowerLevel = edhPowerLevel; + } public boolean isRated() { return rated;