package mage.server.util; import mage.abilities.Ability; import mage.cards.Card; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.choices.Choice; import mage.choices.ChoiceImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.GameCommanderImpl; import mage.game.permanent.Permanent; import mage.players.Player; import mage.util.RandomUtil; import java.io.File; import java.lang.reflect.Constructor; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author JayDi85 */ public final class SystemUtil { private SystemUtil() { } public static final DateFormat dateFormat = new SimpleDateFormat("yy-M-dd HH:mm:ss"); private static final String INIT_FILE_PATH = "config" + File.separator + "init.txt"; private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(SystemUtil.class); private static final String COMMAND_MANA_ADD = "@mana add"; private static final String COMMAND_LANDS_ADD = "@lands add"; private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; private static final String COMMAND_CLEAR_BATTLEFIELD = "@clear battlefield"; private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand"; private static final String COMMAND_SHOW_OPPONENT_LIBRARY = "@show opponent library"; private static final String COMMAND_SHOW_MY_HAND = "@show my hand"; private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library"; private static final Map supportedCommands = new HashMap<>(); static { supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD"); supportedCommands.put(COMMAND_LANDS_ADD, "LANDS ADD"); supportedCommands.put(COMMAND_RUN_CUSTOM_CODE, "RUN CUSTOM CODE"); supportedCommands.put(COMMAND_CLEAR_BATTLEFIELD, "CLAR BATTLEFIELD"); supportedCommands.put(COMMAND_SHOW_OPPONENT_HAND, "SHOW OPPONENT HAND"); supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY"); supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND"); supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY"); } private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card] private static final Pattern patternCommand = Pattern.compile("([\\w]+):([\\S]+?):([\\S ]+):([\\d]+)"); // battlefield:Human:Island:10 private static final Pattern patternCardInfo = Pattern.compile("([\\S ]+):([\\S ]+)"); // Island:XLN // show ext info for special commands private static final String PARAM_COLOR_COST = "color cost"; private static final String PARAM_COLOR_COMMANDER = "color commander"; private static final String PARAM_PT = "pt"; // power toughness private static final String PARAM_ABILITIES_COUNT = "abilities count"; private static final String PARAM_ABILITIES_LIST = "abilities list"; private static class CommandGroup { String name; boolean isSpecialCommand; ArrayList commands = new ArrayList<>(); public CommandGroup(String name) { this(name, false); } public CommandGroup(String name, boolean isSpecialCommand) { this.name = name; this.isSpecialCommand = isSpecialCommand; } public String getPrintName() { if (this.isSpecialCommand && supportedCommands.containsKey(this.name)) { return supportedCommands.get(this.name); } else { return this.name; } } public String getPrintNameWithStats() { String res = this.getPrintName(); if (!this.isSpecialCommand) { res = res + " (" + this.commands.size() + " commands)"; } return res; } } private static String getCardsListForSpecialInform(Game game, Set cardsList, ArrayList commandParams) { return getCardsListForSpecialInform(game, cardsList.stream().collect(Collectors.toList()), commandParams); } private static String getCardsListForSpecialInform(Game game, List cardsList, ArrayList commandParams) { // cards list with ext info ArrayList res = new ArrayList<>(); for (UUID cardID : cardsList) { Card card = game.getCard(cardID); if (card == null) { continue; } // basic info (card + set) String cardInfo = card.getName() + " - " + card.getExpansionSetCode(); // optional info List resInfo = new ArrayList<>(); for (String param : commandParams) { switch (param) { case PARAM_COLOR_COST: resInfo.add(card.getColor(game).toString()); break; case PARAM_COLOR_COMMANDER: resInfo.add(card.getColorIdentity().toString()); break; case PARAM_PT: resInfo.add(card.getPower() + " / " + card.getToughness()); break; case PARAM_ABILITIES_COUNT: resInfo.add(String.valueOf(card.getAbilities(game).size())); break; case PARAM_ABILITIES_LIST: resInfo.add(card.getAbilities(game).stream() .map(Ability::getClass) .map(Class::getSimpleName) .collect(Collectors.joining(", ")) ); break; default: logger.warn("Unknown param for cards list: " + param); } } if (!resInfo.isEmpty()) { cardInfo += ": " + resInfo.stream().collect(Collectors.joining("; ")); } res.add(cardInfo); } return res.stream().sorted().collect(Collectors.joining("\n")); } private static class CardCommandData { public String source; public String zone; public String player; public String cardName; public String cardSet; public Integer Amount; public Boolean OK; public String Error; } public static CardCommandData parseCardCommand(String commandLine) { CardCommandData com = new CardCommandData(); com.source = commandLine.trim(); com.OK = false; com.Error = "unknown error"; Matcher matchCommand = patternCommand.matcher(com.source); if (!matchCommand.matches()) { com.Error = "Unknown command format"; return com; } com.zone = matchCommand.group(1); com.player = matchCommand.group(2); try { com.Amount = Integer.parseInt(matchCommand.group(4)); } catch (Throwable e) { com.Error = "Can't parse amount value [" + matchCommand.group(4) + "]"; return com; } // card name can be with set String cardInfo = matchCommand.group(3); Matcher matchInfo = patternCardInfo.matcher(cardInfo); if (matchInfo.matches()) { // name with set com.cardName = matchInfo.group(1); com.cardSet = matchInfo.group(2); } else { // name only com.cardName = cardInfo; com.cardSet = ""; } if (com.cardName.isEmpty()) { com.Error = "Card name is empty"; return com; } if (com.Amount <= 0) { com.Error = "Amount [" + com.Amount + "] must be greater than 0"; return com; } // all ok com.Error = ""; com.OK = true; return com; } public static void addCardsForTesting(Game game) { addCardsForTesting(game, null, null); } /** * Replaces cards in player's hands by specified in config/init.txt.
*
* Implementation note:
* 1. Read init.txt line by line
* 2. Parse line using for searching groups like: [group 1] * 3. Parse line using the following format: line ::= * :::
* 4. If zone equals to 'hand', add card to player's library
* 5a. Then swap added card with any card in player's hand
* 5b. Parse next line (go to 2.), If EOF go to 4.
* 6. Log message to all players that cards were added (to prevent unfair * play).
* 7. Exit
* * @param game */ public static void addCardsForTesting(Game game, String fileSource, Player feedbackPlayer) { try { String fileName = fileSource; if (fileName == null) { fileName = INIT_FILE_PATH; } File f = new File(fileName); if (!f.exists()) { logger.warn("Couldn't find init file: " + fileName); return; } logger.info("Parsing init file... "); // steps: // 1. parse groups and commands // 2. ask user if many groups // 3. process system commands // 4. run commands from selected group // 1. parse ArrayList groups = new ArrayList<>(); try (Scanner scanner = new Scanner(f)) { CommandGroup currentGroup = null; while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); // skip comments if (line.isEmpty() || line.startsWith("#") || line.startsWith("//")) { continue; } // group Matcher matchGroup = patternGroup.matcher(line); if (matchGroup.matches()) { String groupName = matchGroup.group(1); if (groupName.startsWith("@")) { // special command group if (supportedCommands.containsKey(groupName)) { currentGroup = new CommandGroup(groupName, true); groups.add(currentGroup); } else { logger.warn("Special group [" + groupName + "] is not supported."); } continue; } else { // basic group currentGroup = new CommandGroup(groupName); groups.add(currentGroup); continue; } } // command if (currentGroup == null) { currentGroup = new CommandGroup("default group"); groups.add(currentGroup); } currentGroup.commands.add(line); } } // 2. ask user CommandGroup runGroup = null; if (groups.size() == 1) { // not need to ask runGroup = groups.get(0); } else if (groups.size() > 1) { // need to ask logger.info("Found " + groups.size() + " groups. Need to select."); if (feedbackPlayer != null) { // choice dialog Map list = new LinkedHashMap<>(); Map sort = new LinkedHashMap<>(); for (Integer i = 0; i < groups.size(); i++) { list.put(Integer.toString(i + 1), groups.get(i).getPrintNameWithStats()); sort.put(Integer.toString(i + 1), i); } Choice groupChoice = new ChoiceImpl(false); groupChoice.setMessage("Choose commands group to run"); groupChoice.setKeyChoices(list); groupChoice.setSortData(sort); if (feedbackPlayer.choose(Outcome.Benefit, groupChoice, game)) { String need = groupChoice.getChoiceKey(); if ((need != null) && list.containsKey(need)) { runGroup = groups.get(Integer.parseInt(need) - 1); } } } else { // select default runGroup = groups.get(0); } } if (runGroup == null) { // was canceled logger.info("Command file was empty or canceled"); return; } logger.info("Selected group [" + runGroup.name + "] with " + runGroup.commands.size() + " commands"); // 3. system commands if (runGroup.isSpecialCommand) { Player opponent = game.getPlayer(game.getOpponents(feedbackPlayer.getId()).iterator().next()); switch (runGroup.name) { case COMMAND_SHOW_OPPONENT_HAND: if (opponent != null) { String info = getCardsListForSpecialInform(game, opponent.getHand(), runGroup.commands); game.informPlayer(feedbackPlayer, info); } break; case COMMAND_SHOW_OPPONENT_LIBRARY: if (opponent != null) { String info = getCardsListForSpecialInform(game, opponent.getLibrary().getCardList(), runGroup.commands); game.informPlayer(feedbackPlayer, info); } break; case COMMAND_SHOW_MY_HAND: if (feedbackPlayer != null) { String info = getCardsListForSpecialInform(game, feedbackPlayer.getHand(), runGroup.commands); game.informPlayer(feedbackPlayer, info); } break; case COMMAND_SHOW_MY_LIBRARY: if (feedbackPlayer != null) { String info = getCardsListForSpecialInform(game, feedbackPlayer.getLibrary().getCardList(), runGroup.commands); game.informPlayer(feedbackPlayer, info); } break; } return; } // 4. run commands for (String line : runGroup.commands) { CardCommandData command = parseCardCommand(line); if (!command.OK) { logger.warn(command.Error + ": " + line); continue; } Optional playerOptional = findPlayer(game, command.player); if (!playerOptional.isPresent()) { logger.warn("Unknown player: " + line); continue; } Player player = playerOptional.get(); // SPECIAL token/emblem call (without SET name) if ("token".equalsIgnoreCase(command.zone)) { // eg: token:Human:HippoToken:1 Class c = Class.forName("mage.game.permanent.token." + command.cardName); Constructor cons = c.getConstructor(); Object token = cons.newInstance(); if (token instanceof mage.game.permanent.token.Token) { ((mage.game.permanent.token.Token) token).putOntoBattlefield(command.Amount, game, null, player.getId(), false, false); continue; } } else if ("emblem".equalsIgnoreCase(command.zone)) { // eg: emblem:Human:ElspethSunsChampionEmblem:1 Class c = Class.forName("mage.game.command.emblems." + command.cardName); Constructor cons = c.getConstructor(); Object emblem = cons.newInstance(); if (emblem instanceof mage.game.command.Emblem) { ((mage.game.command.Emblem) emblem).setControllerId(player.getId()); game.addEmblem((mage.game.command.Emblem) emblem, null, player.getId()); continue; } } else if ("plane".equalsIgnoreCase(command.zone)) { // eg: plane:Human:BantPlane:1 Class c = Class.forName("mage.game.command.planes." + command.cardName); Constructor cons = c.getConstructor(); Object plane = cons.newInstance(); if (plane instanceof mage.game.command.Plane) { ((mage.game.command.Plane) plane).setControllerId(player.getId()); game.addPlane((mage.game.command.Plane) plane, null, player.getId()); continue; } } else if ("loyalty".equalsIgnoreCase(command.zone)) { for (Permanent perm : game.getBattlefield().getAllActivePermanents(player.getId())) { if (perm.getName().equals(command.cardName) && perm.getCardType().contains(CardType.PLANESWALKER)) { perm.addCounters(CounterType.LOYALTY.createInstance(command.Amount), null, game); } } continue; } else if ("stack".equalsIgnoreCase(command.zone)) { // simple cast (without targets or modes) // find card info CardInfo cardInfo = CardRepository.instance.findCard(command.cardName); if (cardInfo == null) { logger.warn("Unknown card for stack command [" + command.cardName + "]: " + line); continue; } // put card to game Set cardsToLoad = new HashSet<>(); for (int i = 0; i < command.Amount; i++) { cardsToLoad.add(cardInfo.getCard()); } game.loadCards(cardsToLoad, player.getId()); // move card from exile to stack for (Card card : cardsToLoad) { swapWithAnyCard(game, player, card, Zone.STACK); } continue; } Zone gameZone; if ("hand".equalsIgnoreCase(command.zone)) { gameZone = Zone.HAND; } else if ("battlefield".equalsIgnoreCase(command.zone)) { gameZone = Zone.BATTLEFIELD; } else if ("graveyard".equalsIgnoreCase(command.zone)) { gameZone = Zone.GRAVEYARD; } else if ("library".equalsIgnoreCase(command.zone)) { gameZone = Zone.LIBRARY; } else if ("token".equalsIgnoreCase(command.zone)) { gameZone = Zone.BATTLEFIELD; } else if ("emblem".equalsIgnoreCase(command.zone)) { gameZone = Zone.COMMAND; } else if ("plane".equalsIgnoreCase(command.zone)) { gameZone = Zone.COMMAND; } else if ("commander".equalsIgnoreCase(command.zone)) { gameZone = Zone.COMMAND; } else { logger.warn("Unknown zone [" + command.zone + "]: " + line); continue; } List cards = null; if (command.cardSet.isEmpty()) { // by name cards = CardRepository.instance.findCards(command.cardName); } else { // by name and set cards = CardRepository.instance.findCards(new CardCriteria().setCodes(command.cardSet).name(command.cardName)); } if (cards.isEmpty()) { logger.warn("Unknown card [" + command.cardName + "]: " + line); continue; } Set cardsToLoad = new HashSet<>(); for (int i = 0; i < command.Amount; i++) { CardInfo cardInfo = cards.get(RandomUtil.nextInt(cards.size())); Card card = cardInfo != null ? cardInfo.getCard() : null; if (card != null) { cardsToLoad.add(card); } } game.loadCards(cardsToLoad, player.getId()); if ("commander".equalsIgnoreCase(command.zone) && cardsToLoad.size() > 0) { // as commander (only commander games, look at init code in GameCommanderImpl) if (game instanceof GameCommanderImpl) { GameCommanderImpl gameCommander = (GameCommanderImpl) game; for (Card card : cardsToLoad) { player.addCommanderId(card.getId()); gameCommander.initCommander(card, player); } } else { logger.fatal("Commander card can be used in commander game only: " + command.cardName); } } else { // as other card for (Card card : cardsToLoad) { swapWithAnyCard(game, player, card, gameZone); } } } } catch (Exception e) { logger.fatal("", e); } } /** * Swap cards between specified card from library and any hand card. * * @param game * @param card Card to put to player's hand */ private static void swapWithAnyCard(Game game, Player player, Card card, Zone zone) { // Put the card in Exile to start. Otherwise the game doesn't know where to remove the card from. game.getExile().getPermanentExile().add(card); game.setZone(card.getId(), Zone.EXILED); switch (zone) { case BATTLEFIELD: card.putOntoBattlefield(game, Zone.EXILED, null, player.getId()); break; case LIBRARY: card.setZone(Zone.LIBRARY, game); game.getExile().getPermanentExile().remove(card); player.getLibrary().putOnTop(card, game); break; case STACK: card.cast(game, Zone.EXILED, card.getSpellAbility(), player.getId()); default: card.moveToZone(zone, null, game, false); } logger.info("Added card to player's " + zone.toString() + ": " + card.getName() + ", player = " + player.getName()); } /** * Find player by name. * * @param game * @param name * @return */ private static Optional findPlayer(Game game, String name) { return game.getPlayers().values().stream() .filter(player -> player.getName().equals(name)).findFirst(); } public static String sanitize(String input) { //Pattern pattern = Pattern.compile("[^0-9a-zA-Z]"); //Matcher matcher = pattern.matcher(input); //return matcher.replaceAll(""); return input.replaceAll("[^a-zA-Z0-9]", ""); } /** * Get a diff between two dates * * @param date1 the oldest date * @param date2 the newest date * @param timeUnit the unit in which you want the diff * @return the diff value, in the provided unit */ public static long getDateDiff(Date date1, Date date2, TimeUnit timeUnit) { long diffInMillies = date2.getTime() - date1.getTime(); return timeUnit.convert(diffInMillies, TimeUnit.MILLISECONDS); } }