mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
server: fixed wrong cheater detection in some tourney sideboardings (closes #11877)
This commit is contained in:
parent
72cf60085c
commit
e209ce1c97
17 changed files with 146 additions and 55 deletions
|
|
@ -112,7 +112,7 @@ public class Brawl extends Constructed {
|
||||||
Set<String> basicsInDeck = new HashSet<>();
|
Set<String> basicsInDeck = new HashSet<>();
|
||||||
if (colorIdentity.isColorless()) {
|
if (colorIdentity.isColorless()) {
|
||||||
for (Card card : deck.getCards()) {
|
for (Card card : deck.getCards()) {
|
||||||
if (basicLandNames.contains(card.getName())) {
|
if (ALL_BASIC_LAND_NAMES.contains(card.getName())) {
|
||||||
basicsInDeck.add(card.getName());
|
basicsInDeck.add(card.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -503,15 +503,18 @@ public class TableController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDeck(UUID userId, UUID playerId, Deck deck) {
|
private void updateDeck(UUID userId, UUID playerId, Deck deck) {
|
||||||
|
// strict mode - players can't add any lands while sideboarding in single game
|
||||||
|
// ignore mode - players can add any lands while construction/sideboarding in draft tourney
|
||||||
|
boolean ignoreMainBasicLands = table.isTournament() || table.isTournamentSubTable();
|
||||||
if (table.isTournament()) {
|
if (table.isTournament()) {
|
||||||
if (tournament != null) {
|
if (tournament != null) {
|
||||||
// TODO: is it possible to update from direct call command in game?!
|
// TODO: is it possible to update from direct call command in game?!
|
||||||
managerFactory.tournamentManager().updateDeck(tournament.getId(), playerId, deck);
|
managerFactory.tournamentManager().updateDeck(tournament.getId(), playerId, deck, ignoreMainBasicLands);
|
||||||
} else {
|
} else {
|
||||||
logger.fatal("Tournament == null table: " + table.getId() + " userId: " + userId);
|
logger.fatal("Tournament == null table: " + table.getId() + " userId: " + userId);
|
||||||
}
|
}
|
||||||
} else if (table.getState() == TableState.SIDEBOARDING) {
|
} else if (table.getState() == TableState.SIDEBOARDING) {
|
||||||
match.updateDeck(playerId, deck);
|
match.updateDeck(playerId, deck, ignoreMainBasicLands);
|
||||||
} else {
|
} else {
|
||||||
// deck was meanwhile submitted so the autoupdate can be ignored
|
// deck was meanwhile submitted so the autoupdate can be ignored
|
||||||
// TODO: need research
|
// TODO: need research
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public interface TournamentManager {
|
||||||
|
|
||||||
void submitDeck(UUID tournamentId, UUID playerId, Deck deck);
|
void submitDeck(UUID tournamentId, UUID playerId, Deck deck);
|
||||||
|
|
||||||
void updateDeck(UUID tournamentId, UUID playerId, Deck deck);
|
void updateDeck(UUID tournamentId, UUID playerId, Deck deck, boolean ignoreMainBasicLands);
|
||||||
|
|
||||||
TournamentView getTournamentView(UUID tournamentId);
|
TournamentView getTournamentView(UUID tournamentId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -326,13 +326,13 @@ public class TournamentController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck, boolean ignoreMainBasicLands) {
|
||||||
TournamentSession session = tournamentSessions.getOrDefault(playerId, null);
|
TournamentSession session = tournamentSessions.getOrDefault(playerId, null);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session.updateDeck(deck);
|
session.updateDeck(deck, ignoreMainBasicLands);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void timeout(UUID userId) {
|
public void timeout(UUID userId) {
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,8 @@ public class TournamentManagerImpl implements TournamentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDeck(UUID tournamentId, UUID playerId, Deck deck) {
|
public void updateDeck(UUID tournamentId, UUID playerId, Deck deck, boolean ignoreMainBasicLands) {
|
||||||
controllers.get(tournamentId).updateDeck(playerId, deck);
|
controllers.get(tournamentId).updateDeck(playerId, deck, ignoreMainBasicLands);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ public class TournamentSession {
|
||||||
tournament.submitDeck(playerId, deck);
|
tournament.submitDeck(playerId, deck);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDeck(Deck deck) {
|
public void updateDeck(Deck deck, boolean ignoreMainBasicLands) {
|
||||||
tournament.updateDeck(playerId, deck);
|
tournament.updateDeck(playerId, deck, ignoreMainBasicLands);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKilled() {
|
public void setKilled() {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import mage.cards.decks.Deck;
|
||||||
import mage.cards.decks.DeckCardInfo;
|
import mage.cards.decks.DeckCardInfo;
|
||||||
import mage.cards.decks.DeckCardLayout;
|
import mage.cards.decks.DeckCardLayout;
|
||||||
import mage.cards.decks.DeckCardLists;
|
import mage.cards.decks.DeckCardLists;
|
||||||
|
import mage.cards.repository.CardScanner;
|
||||||
import mage.game.GameException;
|
import mage.game.GameException;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.MageTestPlayerBase;
|
import org.mage.test.serverside.base.MageTestPlayerBase;
|
||||||
|
|
||||||
|
|
@ -24,12 +26,25 @@ public class DeckHashTest extends MageTestPlayerBase {
|
||||||
private static final DeckCardInfo GOOD_CARD_1_x3 = new DeckCardInfo("Lightning Bolt", "141", "CLU", 3);
|
private static final DeckCardInfo GOOD_CARD_1_x3 = new DeckCardInfo("Lightning Bolt", "141", "CLU", 3);
|
||||||
private static final DeckCardInfo GOOD_CARD_2 = new DeckCardInfo("Bear's Companion", "182", "2x2");
|
private static final DeckCardInfo GOOD_CARD_2 = new DeckCardInfo("Bear's Companion", "182", "2x2");
|
||||||
private static final DeckCardInfo GOOD_CARD_3 = new DeckCardInfo("Amplifire", "92", "RNA");
|
private static final DeckCardInfo GOOD_CARD_3 = new DeckCardInfo("Amplifire", "92", "RNA");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_4_BASIC_LAND = new DeckCardInfo("Forest", "276", "XLN");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_5_BASIC_LAND = new DeckCardInfo("Plains", "260", "XLN");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_6_BASIC_LAND_SNOW = new DeckCardInfo("Snow-Covered Forest", "383", "ICE");
|
||||||
|
private static final DeckCardInfo GOOD_CARD_7_WASTES = new DeckCardInfo("Wastes", "408", "M3C");
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
CardScanner.scan();
|
||||||
|
}
|
||||||
|
|
||||||
private void assertDecks(String check, boolean mustBeSame, Deck deck1, Deck deck2) {
|
private void assertDecks(String check, boolean mustBeSame, Deck deck1, Deck deck2) {
|
||||||
|
assertDecks(check, mustBeSame, deck1, deck2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDecks(String check, boolean mustBeSame, Deck deck1, Deck deck2, boolean ignoreBasicLands) {
|
||||||
Assert.assertEquals(
|
Assert.assertEquals(
|
||||||
check + " - " + (mustBeSame ? "hash code must be same" : "hash code must be different"),
|
check + " - " + (mustBeSame ? "hash code must be same" : "hash code must be different"),
|
||||||
mustBeSame,
|
mustBeSame,
|
||||||
deck1.getDeckHash() == deck2.getDeckHash()
|
deck1.getDeckHash(ignoreBasicLands) == deck2.getDeckHash(ignoreBasicLands)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,4 +253,78 @@ public class DeckHashTest extends MageTestPlayerBase {
|
||||||
}
|
}
|
||||||
Assert.fail("must raise exception on too big amount");
|
Assert.fail("must raise exception on too big amount");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_IgnoreBasicLands() {
|
||||||
|
// related rules:
|
||||||
|
// Players may add an unlimited number of cards named Plains, Island, Swamp, Mountain, or Forest to their
|
||||||
|
// deck and sideboard. They may not add additional snow basic land cards (e.g., Snow-Covered Forest, etc)
|
||||||
|
// or Wastes basic land cards, even in formats in which they are legal.
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same basic lands - strict mode",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"same basic lands - ignore mode",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff basic lands - strict mode",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_5_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff basic lands - ignore mode",
|
||||||
|
true,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_5_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// snow covered and wastes must be checked as normal cards (not ignore)
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff snow-covered basic lands - strict mode",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_6_BASIC_LAND_SNOW, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff snow-covered basic lands - ignore mode",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_6_BASIC_LAND_SNOW, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff wastes basic lands - strict mode",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_7_WASTES, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDecks(
|
||||||
|
"diff wastes basic lands - ignore mode",
|
||||||
|
false,
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_4_BASIC_LAND, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
prepareCardsDeck(Arrays.asList(GOOD_CARD_1, GOOD_CARD_7_WASTES, GOOD_CARD_2), Arrays.asList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ public class TournamentStub implements Tournament {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck, boolean ignoreMainBasicLands) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -254,10 +254,11 @@ public class Deck implements Serializable, Copyable<Deck> {
|
||||||
return new Deck(this);
|
return new Deck(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDeckHash() {
|
public long getDeckHash(boolean ignoreMainBasicLands) {
|
||||||
return DeckUtil.getDeckHash(
|
return DeckUtil.getDeckHash(
|
||||||
this.cards.stream().map(MageObject::getName).collect(Collectors.toList()),
|
this.cards.stream().map(MageObject::getName).collect(Collectors.toList()),
|
||||||
this.sideboard.stream().map(MageObject::getName).collect(Collectors.toList())
|
this.sideboard.stream().map(MageObject::getName).collect(Collectors.toList()),
|
||||||
|
ignoreMainBasicLands
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,15 @@ import java.util.stream.Collectors;
|
||||||
*/
|
*/
|
||||||
public abstract class DeckValidator implements Serializable {
|
public abstract class DeckValidator implements Serializable {
|
||||||
|
|
||||||
protected static final List<String> basicLandNames = Arrays.asList(
|
public static final HashSet<String> MAIN_BASIC_LAND_NAMES = new HashSet<>(Arrays.asList(
|
||||||
"Plains",
|
"Plains",
|
||||||
"Island",
|
"Island",
|
||||||
"Swamp",
|
"Swamp",
|
||||||
"Mountain",
|
"Mountain",
|
||||||
"Forest",
|
"Forest"
|
||||||
|
));
|
||||||
|
|
||||||
|
public static final HashSet<String> ADDITIONAL_BASIC_LAND_NAMES = new HashSet<>(Arrays.asList(
|
||||||
"Wastes",
|
"Wastes",
|
||||||
"Snow-Covered Plains",
|
"Snow-Covered Plains",
|
||||||
"Snow-Covered Island",
|
"Snow-Covered Island",
|
||||||
|
|
@ -24,11 +27,19 @@ public abstract class DeckValidator implements Serializable {
|
||||||
"Snow-Covered Mountain",
|
"Snow-Covered Mountain",
|
||||||
"Snow-Covered Forest",
|
"Snow-Covered Forest",
|
||||||
"Snow-Covered Wastes"
|
"Snow-Covered Wastes"
|
||||||
);
|
));
|
||||||
|
|
||||||
|
public static final HashSet<String> ALL_BASIC_LAND_NAMES = new HashSet<>();
|
||||||
|
{
|
||||||
|
ALL_BASIC_LAND_NAMES.addAll(MAIN_BASIC_LAND_NAMES);
|
||||||
|
ALL_BASIC_LAND_NAMES.addAll(ADDITIONAL_BASIC_LAND_NAMES);
|
||||||
|
}
|
||||||
|
|
||||||
protected static final Map<String, Integer> maxCopiesMap = new HashMap<>();
|
protected static final Map<String, Integer> maxCopiesMap = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
basicLandNames.stream().forEach(s -> maxCopiesMap.put(s, Integer.MAX_VALUE));
|
MAIN_BASIC_LAND_NAMES.forEach(s -> maxCopiesMap.put(s, Integer.MAX_VALUE));
|
||||||
|
ADDITIONAL_BASIC_LAND_NAMES.forEach(s -> maxCopiesMap.put(s, Integer.MAX_VALUE));
|
||||||
maxCopiesMap.put("Relentless Rats", Integer.MAX_VALUE);
|
maxCopiesMap.put("Relentless Rats", Integer.MAX_VALUE);
|
||||||
maxCopiesMap.put("Shadowborn Apostle", Integer.MAX_VALUE);
|
maxCopiesMap.put("Shadowborn Apostle", Integer.MAX_VALUE);
|
||||||
maxCopiesMap.put("Rat Colony", Integer.MAX_VALUE);
|
maxCopiesMap.put("Rat Colony", Integer.MAX_VALUE);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ public interface Match {
|
||||||
|
|
||||||
void submitDeck(UUID playerId, Deck deck);
|
void submitDeck(UUID playerId, Deck deck);
|
||||||
|
|
||||||
void updateDeck(UUID playerId, Deck deck);
|
void updateDeck(UUID playerId, Deck deck, boolean ignoreMainBasicLands);
|
||||||
|
|
||||||
void startMatch();
|
void startMatch();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -389,14 +389,14 @@ public abstract class MatchImpl implements Match {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck, boolean ignoreMainBasicLands) {
|
||||||
// used for auto-save deck
|
// used for auto-save deck
|
||||||
MatchPlayer player = getPlayer(playerId);
|
MatchPlayer player = getPlayer(playerId);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.updateDeck(deck);
|
player.updateDeck(deck, ignoreMainBasicLands);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String createGameStartMessage() {
|
protected String createGameStartMessage() {
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class MatchPlayer implements Serializable {
|
||||||
this.doneSideboarding = true;
|
this.doneSideboarding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateDeck(Deck newDeck) {
|
public boolean updateDeck(Deck newDeck, boolean ignoreMainBasicLands) {
|
||||||
// used for auto-save by timeout from client side
|
// used for auto-save by timeout from client side
|
||||||
|
|
||||||
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
// workaround to keep deck name for Tiny Leaders because it must be hidden for players
|
||||||
|
|
@ -106,7 +106,7 @@ public class MatchPlayer implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure it's the same deck (player do not add or remove something)
|
// make sure it's the same deck (player do not add or remove something)
|
||||||
boolean isGood = (this.deck.getDeckHash() == newDeck.getDeckHash());
|
boolean isGood = (this.deck.getDeckHash(ignoreMainBasicLands) == newDeck.getDeckHash(ignoreMainBasicLands));
|
||||||
if (!isGood) {
|
if (!isGood) {
|
||||||
logger.error("Found cheating player " + player.getName()
|
logger.error("Found cheating player " + player.getName()
|
||||||
+ " with changed deck, main " + newDeck.getCards().size() + ", side " + newDeck.getSideboard().size());
|
+ " with changed deck, main " + newDeck.getCards().size() + ", side " + newDeck.getSideboard().size());
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ public interface Tournament {
|
||||||
|
|
||||||
void submitDeck(UUID playerId, Deck deck);
|
void submitDeck(UUID playerId, Deck deck);
|
||||||
|
|
||||||
void updateDeck(UUID playerId, Deck deck);
|
void updateDeck(UUID playerId, Deck deck, boolean ignoreMainBasicLands);
|
||||||
|
|
||||||
void autoSubmit(UUID playerId, Deck deck);
|
void autoSubmit(UUID playerId, Deck deck);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,13 +139,13 @@ public abstract class TournamentImpl implements Tournament {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDeck(UUID playerId, Deck deck) {
|
public void updateDeck(UUID playerId, Deck deck, boolean ignoreMainBasicLands) {
|
||||||
TournamentPlayer player = players.getOrDefault(playerId, null);
|
TournamentPlayer player = players.getOrDefault(playerId, null);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.updateDeck(deck);
|
player.updateDeck(deck, ignoreMainBasicLands);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Round createRoundRandom() {
|
protected Round createRoundRandom() {
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,11 @@ public class TournamentPlayer {
|
||||||
this.setState(TournamentPlayerState.WAITING);
|
this.setState(TournamentPlayerState.WAITING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateDeck(Deck deck) {
|
public boolean updateDeck(Deck deck, boolean ignoreMainBasicLands) {
|
||||||
// used for auto-save deck
|
// used for auto-save deck
|
||||||
|
|
||||||
// make sure it's the same deck (player do not add or remove something)
|
// make sure it's the same deck (player do not add or remove something)
|
||||||
boolean isGood = (this.getDeck().getDeckHash() == deck.getDeckHash());
|
boolean isGood = (this.getDeck().getDeckHash(ignoreMainBasicLands) == deck.getDeckHash(ignoreMainBasicLands));
|
||||||
if (!isGood) {
|
if (!isGood) {
|
||||||
logger.error("Found cheating tourney player " + player.getName()
|
logger.error("Found cheating tourney player " + player.getName()
|
||||||
+ " with changed deck, main " + deck.getCards().size() + ", side " + deck.getSideboard().size());
|
+ " with changed deck, main " + deck.getCards().size() + ", side " + deck.getSideboard().size());
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package mage.util;
|
package mage.util;
|
||||||
|
|
||||||
import mage.cards.decks.Deck;
|
import mage.cards.decks.DeckValidator;
|
||||||
import mage.players.Player;
|
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
|
|
@ -22,10 +21,22 @@ public final class DeckUtil {
|
||||||
/**
|
/**
|
||||||
* Find deck hash (main + sideboard)
|
* Find deck hash (main + sideboard)
|
||||||
* It must be same after sideboard (do not depend on main/sb structure)
|
* It must be same after sideboard (do not depend on main/sb structure)
|
||||||
|
*
|
||||||
|
* @param ignoreMainBasicLands - drafts allow to use any basic lands, so ignore it for hash calculation
|
||||||
*/
|
*/
|
||||||
public static long getDeckHash(List<String> mainCards, List<String> sideboardCards) {
|
public static long getDeckHash(List<String> mainCards, List<String> sideboardCards, boolean ignoreMainBasicLands) {
|
||||||
List<String> all = new ArrayList<>(mainCards);
|
List<String> all = new ArrayList<>(mainCards);
|
||||||
all.addAll(sideboardCards);
|
all.addAll(sideboardCards);
|
||||||
|
|
||||||
|
// related rules:
|
||||||
|
// 7.2 Card Use in Limited Tournaments
|
||||||
|
// Players may add an unlimited number of cards named Plains, Island, Swamp, Mountain, or Forest to their
|
||||||
|
// deck and sideboard. They may not add additional snow basic land cards (e.g., Snow-Covered Forest, etc)
|
||||||
|
// or Wastes basic land cards, even in formats in which they are legal.
|
||||||
|
if (ignoreMainBasicLands) {
|
||||||
|
all.removeIf(DeckValidator.MAIN_BASIC_LAND_NAMES::contains);
|
||||||
|
}
|
||||||
|
|
||||||
Collections.sort(all);
|
Collections.sort(all);
|
||||||
return getStringHash(all.toString());
|
return getStringHash(all.toString());
|
||||||
}
|
}
|
||||||
|
|
@ -58,27 +69,4 @@ public final class DeckUtil {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* make sure it's the same deck (player do not add or remove something)
|
|
||||||
*
|
|
||||||
* @param newDeck will be clear on cheating
|
|
||||||
*/
|
|
||||||
public boolean checkDeckForModification(String checkInfo, Player player, Deck oldDeck, Deck newDeck) {
|
|
||||||
boolean isGood = (oldDeck.getDeckHash() == newDeck.getDeckHash());
|
|
||||||
if (!isGood) {
|
|
||||||
String message = String.format("Found cheating player [%s] in [%s] with changed deck, main %d -> %d, side %d -> %d",
|
|
||||||
player.getName(),
|
|
||||||
checkInfo,
|
|
||||||
oldDeck.getCards().size(),
|
|
||||||
newDeck.getCards().size(),
|
|
||||||
oldDeck.getSideboard().size(),
|
|
||||||
newDeck.getSideboard().size()
|
|
||||||
);
|
|
||||||
logger.error(message);
|
|
||||||
newDeck.getCards().clear();
|
|
||||||
newDeck.getSideboard().clear();
|
|
||||||
}
|
|
||||||
return isGood;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue