mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Initial checkin for Puzzle Mode
This commit is contained in:
9
.gitattributes
vendored
9
.gitattributes
vendored
@@ -993,6 +993,11 @@ forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.ja
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/online/OnlineMenu.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/online/VSubmenuOnlineLobby.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/package-info.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleCreate.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/CSubmenuPuzzleSolve.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/PuzzleGameMenu.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuPuzzleCreate.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/puzzle/VSubmenuPuzzleSolve.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuChallenges.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuDuels.java -text
|
||||
forge-gui-desktop/src/main/java/forge/screens/home/quest/CSubmenuQuestData.java -text
|
||||
@@ -1347,6 +1352,7 @@ forge-gui-mobile/src/forge/screens/gauntlet/NewGauntletScreen.java -text
|
||||
forge-gui-mobile/src/forge/screens/home/HomeScreen.java -text
|
||||
forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java -text
|
||||
forge-gui-mobile/src/forge/screens/home/NewGameMenu.java -text
|
||||
forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java -text
|
||||
forge-gui-mobile/src/forge/screens/limited/DraftingProcessScreen.java -text
|
||||
forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java -text
|
||||
forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.java -text
|
||||
@@ -18806,6 +18812,7 @@ forge-gui/res/music/menus/Evil[!!-~]March.mp3 -text
|
||||
forge-gui/res/music/menus/Heroic[!!-~]Age.mp3 -text
|
||||
forge-gui/res/music/menus/Lord[!!-~]of[!!-~]the[!!-~]Land.mp3 -text
|
||||
forge-gui/res/music/menus/The[!!-~]Pyre.mp3 -text
|
||||
forge-gui/res/puzzle/PS1.pzl -text
|
||||
forge-gui/res/quest/bazaar/ape_pet_l1.txt -text
|
||||
forge-gui/res/quest/bazaar/ape_pet_l2.txt -text
|
||||
forge-gui/res/quest/bazaar/ape_pet_l3.txt -text
|
||||
@@ -20844,6 +20851,8 @@ forge-gui/src/main/java/forge/player/package-info.java -text
|
||||
forge-gui/src/main/java/forge/properties/ForgeConstants.java -text
|
||||
forge-gui/src/main/java/forge/properties/ForgePreferences.java svneol=native#text/plain
|
||||
forge-gui/src/main/java/forge/properties/package-info.java svneol=native#text/plain
|
||||
forge-gui/src/main/java/forge/puzzle/Puzzle.java -text
|
||||
forge-gui/src/main/java/forge/puzzle/PuzzleIO.java -text
|
||||
forge-gui/src/main/java/forge/quest/BoosterUtils.java svneol=native#text/plain
|
||||
forge-gui/src/main/java/forge/quest/IQuestEvent.java -text
|
||||
forge-gui/src/main/java/forge/quest/IQuestRewardCard.java -text
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.effects.DetachedCardEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -31,7 +32,7 @@ import forge.util.collect.FCollectionView;
|
||||
public abstract class GameState {
|
||||
private static final Map<ZoneType, String> ZONES = new HashMap<ZoneType, String>();
|
||||
static {
|
||||
ZONES.put(ZoneType.Battlefield, "play");
|
||||
ZONES.put(ZoneType.Battlefield, "battlefield");
|
||||
ZONES.put(ZoneType.Hand, "hand");
|
||||
ZONES.put(ZoneType.Graveyard, "graveyard");
|
||||
ZONES.put(ZoneType.Library, "library");
|
||||
@@ -41,8 +42,15 @@ public abstract class GameState {
|
||||
|
||||
private int humanLife = -1;
|
||||
private int computerLife = -1;
|
||||
private String humanCounters = "";
|
||||
private String computerCounters = "";
|
||||
|
||||
private final Map<ZoneType, String> humanCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
|
||||
private final Map<ZoneType, String> aiCardTexts = new EnumMap<ZoneType, String>(ZoneType.class);
|
||||
|
||||
private final Map<Integer, Card> idToCard = new HashMap<>();
|
||||
private final Map<Card, Integer> cardToAttachId = new HashMap<>();
|
||||
|
||||
private String tChangePlayer = "NONE";
|
||||
private String tChangePhase = "NONE";
|
||||
|
||||
@@ -56,6 +64,14 @@ public abstract class GameState {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("humanlife=%d\n", humanLife));
|
||||
sb.append(String.format("ailife=%d\n", computerLife));
|
||||
|
||||
if (!humanCounters.isEmpty()) {
|
||||
sb.append(String.format("humancounters=%s\n", humanCounters));
|
||||
}
|
||||
if (!computerCounters.isEmpty()) {
|
||||
sb.append(String.format("aicounters=%s\n", computerCounters));
|
||||
}
|
||||
|
||||
sb.append(String.format("activeplayer=%s\n", tChangePlayer));
|
||||
sb.append(String.format("activephase=%s\n", tChangePhase));
|
||||
appendCards(humanCardTexts, "human", sb);
|
||||
@@ -65,7 +81,7 @@ public abstract class GameState {
|
||||
|
||||
private void appendCards(Map<ZoneType, String> cardTexts, String categoryPrefix, StringBuilder sb) {
|
||||
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
|
||||
sb.append(String.format("%scardsin%s=%s\n", categoryPrefix, ZONES.get(kv.getKey()), kv.getValue()));
|
||||
sb.append(String.format("%s%s=%s\n", categoryPrefix, ZONES.get(kv.getKey()), kv.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +98,9 @@ public abstract class GameState {
|
||||
}
|
||||
humanLife = human.getLife();
|
||||
computerLife = ai.getLife();
|
||||
humanCounters = countersToString(human.getCounters());
|
||||
computerCounters = countersToString(ai.getCounters());
|
||||
|
||||
tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human";
|
||||
tChangePhase = game.getPhaseHandler().getPhase().toString();
|
||||
aiCardTexts.clear();
|
||||
@@ -111,43 +130,61 @@ public abstract class GameState {
|
||||
newText.append(c.getPaperCard().getName());
|
||||
}
|
||||
if (c.isCommander()) {
|
||||
newText.append("|IsCommander:True");
|
||||
newText.append("|IsCommander");
|
||||
}
|
||||
if (zoneType == ZoneType.Battlefield) {
|
||||
if (c.isTapped()) {
|
||||
newText.append("|Tapped:True");
|
||||
newText.append("|Tapped");
|
||||
}
|
||||
if (c.isSick()) {
|
||||
newText.append("|SummonSick:True");
|
||||
newText.append("|SummonSick");
|
||||
}
|
||||
if (c.isFaceDown()) {
|
||||
newText.append("|FaceDown:True");
|
||||
newText.append("|FaceDown");
|
||||
if (c.isManifested()) {
|
||||
newText.append(":Manifested");
|
||||
}
|
||||
}
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
if (!counters.isEmpty()) {
|
||||
newText.append("|Counters:");
|
||||
boolean start = true;
|
||||
for(Entry<CounterType, Integer> kv : counters.entrySet()) {
|
||||
String str = kv.getKey().toString();
|
||||
int count = kv.getValue();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!start) {
|
||||
newText.append(",");
|
||||
}
|
||||
newText.append(str);
|
||||
start = false;
|
||||
}
|
||||
}
|
||||
newText.append(countersToString(counters));
|
||||
}
|
||||
if (c.getEquipping() != null) {
|
||||
newText.append("|Attaching:").append(c.getEquipping().getId());
|
||||
} else if (c.getFortifying() != null) {
|
||||
newText.append("|Attaching:").append(c.getFortifying().getId());
|
||||
} else if (c.getEnchantingCard() != null) {
|
||||
newText.append("|Attaching:").append(c.getEnchantingCard().getId());
|
||||
}
|
||||
|
||||
if (!c.getEnchantedBy(false).isEmpty() || !c.getEquippedBy(false).isEmpty() || !c.getFortifiedBy(false).isEmpty()) {
|
||||
newText.append("|Id:").append(c.getId());
|
||||
}
|
||||
}
|
||||
cardTexts.put(zoneType, newText.toString());
|
||||
}
|
||||
|
||||
private String[] parseLine(String line) {
|
||||
private String countersToString(Map<CounterType, Integer> counters) {
|
||||
boolean first = true;
|
||||
StringBuilder counterString = new StringBuilder();
|
||||
|
||||
for(Entry<CounterType, Integer> kv : counters.entrySet()) {
|
||||
if (!first) {
|
||||
counterString.append(",");
|
||||
}
|
||||
|
||||
first = false;
|
||||
counterString.append(String.format("%s=%d", kv.getKey().toString(), kv.getValue()));
|
||||
}
|
||||
return counterString.toString();
|
||||
}
|
||||
|
||||
private String[] splitLine(String line) {
|
||||
if (line.charAt(0) == '#') {
|
||||
return null;
|
||||
}
|
||||
final String[] tempData = line.split("=");
|
||||
final String[] tempData = line.split("=", 2);
|
||||
if (tempData.length >= 2) {
|
||||
return tempData;
|
||||
}
|
||||
@@ -163,32 +200,91 @@ public abstract class GameState {
|
||||
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
String[] keyValue = parseLine(line);
|
||||
if (keyValue == null) {
|
||||
continue;
|
||||
}
|
||||
final String categoryName = keyValue[0].toLowerCase();
|
||||
final String categoryValue = keyValue[1];
|
||||
parseLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryName.equals("humanlife")) humanLife = Integer.parseInt(categoryValue);
|
||||
else if (categoryName.equals("ailife")) computerLife = Integer.parseInt(categoryValue);
|
||||
public void parse(List<String> lines) {
|
||||
for(String line : lines) {
|
||||
parseLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
else if (categoryName.equals("activeplayer")) tChangePlayer = categoryValue.trim().toLowerCase();
|
||||
else if (categoryName.equals("activephase")) tChangePhase = categoryValue;
|
||||
protected void parseLine(String line) {
|
||||
String[] keyValue = splitLine(line);
|
||||
if (keyValue == null) return;
|
||||
|
||||
else if (categoryName.equals("humancardsinplay")) humanCardTexts.put(ZoneType.Battlefield, categoryValue);
|
||||
else if (categoryName.equals("aicardsinplay")) aiCardTexts.put(ZoneType.Battlefield, categoryValue);
|
||||
else if (categoryName.equals("humancardsinhand")) humanCardTexts.put(ZoneType.Hand, categoryValue);
|
||||
else if (categoryName.equals("aicardsinhand")) aiCardTexts.put(ZoneType.Hand, categoryValue);
|
||||
else if (categoryName.equals("humancardsingraveyard")) humanCardTexts.put(ZoneType.Graveyard, categoryValue);
|
||||
else if (categoryName.equals("aicardsingraveyard")) aiCardTexts.put(ZoneType.Graveyard, categoryValue);
|
||||
else if (categoryName.equals("humancardsinlibrary")) humanCardTexts.put(ZoneType.Library, categoryValue);
|
||||
else if (categoryName.equals("aicardsinlibrary")) aiCardTexts.put(ZoneType.Library, categoryValue);
|
||||
else if (categoryName.equals("humancardsinexile")) humanCardTexts.put(ZoneType.Exile, categoryValue);
|
||||
else if (categoryName.equals("aicardsinexile")) aiCardTexts.put(ZoneType.Exile, categoryValue);
|
||||
else if (categoryName.equals("humancardsincommand")) humanCardTexts.put(ZoneType.Command, categoryValue);
|
||||
else if (categoryName.equals("aicardsincommand")) aiCardTexts.put(ZoneType.Command, categoryValue);
|
||||
else System.out.println("Unknown key: " + categoryName);
|
||||
final String categoryName = keyValue[0].toLowerCase();
|
||||
final String categoryValue = keyValue[1];
|
||||
|
||||
if (categoryName.startsWith("active")) {
|
||||
if (categoryName.endsWith("player"))
|
||||
tChangePlayer = categoryValue.trim().toLowerCase();
|
||||
if (categoryName.endsWith("phase"))
|
||||
tChangePhase = categoryValue.trim().toUpperCase();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isHuman = categoryName.startsWith("human");
|
||||
|
||||
if (categoryName.endsWith("life")) {
|
||||
if (isHuman)
|
||||
humanLife = Integer.parseInt(categoryValue);
|
||||
else
|
||||
computerLife = Integer.parseInt(categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("counters")) {
|
||||
if (isHuman)
|
||||
humanCounters = categoryValue;
|
||||
else
|
||||
computerCounters = categoryValue;
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Battlefield, categoryValue);
|
||||
else
|
||||
aiCardTexts.put(ZoneType.Battlefield, categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("hand")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Hand, categoryValue);
|
||||
else
|
||||
aiCardTexts.put(ZoneType.Hand, categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.endsWith("graveyard")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Graveyard, categoryValue);
|
||||
else
|
||||
aiCardTexts.put(ZoneType.Graveyard, categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.equals("library")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Library, categoryValue);
|
||||
else
|
||||
aiCardTexts.put(ZoneType.Library, categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.equals("exile")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Exile, categoryValue);
|
||||
else
|
||||
aiCardTexts.put(ZoneType.Exile, categoryValue);
|
||||
}
|
||||
|
||||
else if (categoryName.equals("command")) {
|
||||
if (isHuman)
|
||||
humanCardTexts.put(ZoneType.Command, categoryValue);
|
||||
else
|
||||
aiCardTexts.put(ZoneType.Command, categoryValue);
|
||||
}
|
||||
|
||||
else {
|
||||
System.out.println("Unknown key: " + categoryName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,27 +292,54 @@ public abstract class GameState {
|
||||
game.getAction().invoke(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Player human = game.getPlayers().get(0);
|
||||
final Player ai = game.getPlayers().get(1);
|
||||
|
||||
Player newPlayerTurn = tChangePlayer.equals("human") ? newPlayerTurn = human : tChangePlayer.equals("ai") ? newPlayerTurn = ai : null;
|
||||
PhaseType newPhase = tChangePhase.trim().equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase);
|
||||
|
||||
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn);
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
|
||||
setupPlayerState(humanLife, humanCardTexts, human);
|
||||
setupPlayerState(computerLife, aiCardTexts, ai);
|
||||
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
applyGameOnThread(game);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void applyGameOnThread(final Game game) {
|
||||
final Player human = game.getPlayers().get(0);
|
||||
final Player ai = game.getPlayers().get(1);
|
||||
|
||||
Player newPlayerTurn = tChangePlayer.equals("human") ? human : tChangePlayer.equals("ai") ? ai : null;
|
||||
PhaseType newPhase = tChangePhase.equals("none") ? null : PhaseType.smartValueOf(tChangePhase);
|
||||
|
||||
// Set stack to resolving so things won't trigger/effects be checked right away
|
||||
game.getStack().setResolving(true);
|
||||
|
||||
if (!humanCounters.isEmpty()) {
|
||||
applyCountersToGameEntity(human, humanCounters);
|
||||
}
|
||||
if (!computerCounters.isEmpty()) {
|
||||
applyCountersToGameEntity(ai, computerCounters);
|
||||
}
|
||||
game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn);
|
||||
|
||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||
|
||||
setupPlayerState(humanLife, humanCardTexts, human);
|
||||
setupPlayerState(computerLife, aiCardTexts, ai);
|
||||
|
||||
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
|
||||
|
||||
game.getStack().setResolving(false);
|
||||
|
||||
game.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
|
||||
}
|
||||
|
||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||
entity.setCounters(new HashMap<CounterType, Integer>());
|
||||
String[] allCounterStrings = counterString.split(",");
|
||||
for (final String counterPair : allCounterStrings) {
|
||||
String[] pair = counterPair.split("=", 2);
|
||||
|
||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPlayerState(int life, Map<ZoneType, String> cardTexts, final Player p) {
|
||||
// Lock check static as we setup player state
|
||||
|
||||
Map<ZoneType, CardCollectionView> playerCards = new EnumMap<ZoneType, CardCollectionView>(ZoneType.class);
|
||||
for (Entry<ZoneType, String> kv : cardTexts.entrySet()) {
|
||||
String value = kv.getValue();
|
||||
@@ -245,7 +368,12 @@ public abstract class GameState {
|
||||
// var as-is.
|
||||
c.setCounters(new HashMap<CounterType, Integer>());
|
||||
p.getZone(ZoneType.Hand).add(c);
|
||||
p.getGame().getAction().moveToPlay(c);
|
||||
if (c.isAura()) {
|
||||
p.getGame().getAction().moveToPlay(c);
|
||||
} else {
|
||||
p.getGame().getAction().moveToPlay(c);
|
||||
}
|
||||
|
||||
c.setTapped(tapped);
|
||||
c.setSickness(sickness);
|
||||
c.setCounters(counters);
|
||||
@@ -254,6 +382,19 @@ public abstract class GameState {
|
||||
zone.setCards(kv.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
for(Entry<Card, Integer> entry : cardToAttachId.entrySet()) {
|
||||
Card attachedTo = idToCard.get(entry.getValue());
|
||||
Card attacher = entry.getKey();
|
||||
|
||||
if (attacher.isEquipment()) {
|
||||
attacher.equipCard(attachedTo);
|
||||
} else if (attacher.isAura()) {
|
||||
attacher.enchantEntity(attachedTo);
|
||||
} else if (attacher.isFortified()) {
|
||||
attacher.fortifyCard(attachedTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,7 +406,7 @@ public abstract class GameState {
|
||||
* an array of {@link java.lang.String} objects.
|
||||
* @param player
|
||||
* a {@link forge.game.player.Player} object.
|
||||
* @return a {@link forge.CardList} object.
|
||||
* @return a {@link CardCollectionView} object.
|
||||
*/
|
||||
private CardCollectionView processCardsForZone(final String[] data, final Player player) {
|
||||
final CardCollection cl = new CardCollection();
|
||||
@@ -286,22 +427,29 @@ public abstract class GameState {
|
||||
if (info.startsWith("Set:")) {
|
||||
c.setSetCode(info.substring(info.indexOf(':') + 1));
|
||||
hasSetCurSet = true;
|
||||
} else if (info.equalsIgnoreCase("Tapped:True")) {
|
||||
}
|
||||
else if (info.startsWith("Tapped")) {
|
||||
c.tap();
|
||||
} else if (info.startsWith("Counters:")) {
|
||||
final String[] counterStrings = info.substring(info.indexOf(':') + 1).split(",");
|
||||
for (final String counter : counterStrings) {
|
||||
c.addCounter(CounterType.valueOf(counter), 1, true);
|
||||
}
|
||||
} else if (info.equalsIgnoreCase("SummonSick:True")) {
|
||||
applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1));
|
||||
} else if (info.startsWith("SummonSick")) {
|
||||
c.setSickness(true);
|
||||
} else if (info.equalsIgnoreCase("FaceDown:True")) {
|
||||
} else if (info.startsWith("FaceDown")) {
|
||||
c.setState(CardStateName.FaceDown, true);
|
||||
} else if (info.equalsIgnoreCase("IsCommander:True")) {
|
||||
if (info.endsWith("Manifested")) {
|
||||
c.setManifested(true);
|
||||
}
|
||||
} else if (info.startsWith("IsCommander")) {
|
||||
// TODO: This doesn't seem to properly restore the ability to play the commander. Why?
|
||||
c.setCommander(true);
|
||||
player.setCommanders(Lists.newArrayList(c));
|
||||
player.getZone(ZoneType.Command).add(Player.createCommanderEffect(player.getGame(), c));
|
||||
} else if (info.startsWith("Id:")) {
|
||||
int id = Integer.parseInt(info.substring(4));
|
||||
idToCard.put(id, c);
|
||||
} else if (info.startsWith("Attaching:")) {
|
||||
int id = Integer.parseInt(info.substring(info.indexOf(':') + 1));
|
||||
cardToAttachId.put(c, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,13 +94,15 @@ public enum DeckFormat {
|
||||
PlanarConquest ( Range.between(40, Integer.MAX_VALUE), Range.is(0), 1),
|
||||
Vanguard ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||
Planechase ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||
Archenemy ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4);
|
||||
Archenemy ( Range.between(60, Integer.MAX_VALUE), Range.is(0), 4),
|
||||
Puzzle ( Range.between(0, Integer.MAX_VALUE), Range.is(0), 4);
|
||||
|
||||
private final Range<Integer> mainRange;
|
||||
private final Range<Integer> sideRange; // null => no check
|
||||
private final int maxCardCopies;
|
||||
private final Predicate<CardRules> cardPoolFilter;
|
||||
private final static String ADVPROCLAMATION = "Advantageous Proclamation";
|
||||
private final static String SOVREALM = "Sovereign's Realm";
|
||||
|
||||
private DeckFormat(Range<Integer> mainRange0, Range<Integer> sideRange0, int maxCardCopies0) {
|
||||
this(mainRange0, sideRange0, maxCardCopies0, null);
|
||||
@@ -168,11 +170,13 @@ public enum DeckFormat {
|
||||
|
||||
int min = getMainRange().getMinimum();
|
||||
int max = getMainRange().getMaximum();
|
||||
boolean noBasicLands = false;
|
||||
|
||||
// Adjust minimum base on number of Advantageous Proclamation or similar cards
|
||||
CardPool conspiracies = deck.get(DeckSection.Conspiracy);
|
||||
if (conspiracies != null) {
|
||||
min -= (5 * conspiracies.countByName(ADVPROCLAMATION, true));
|
||||
min -= (5 * conspiracies.countByName(ADVPROCLAMATION, false));
|
||||
noBasicLands = conspiracies.countByName(SOVREALM, false) > 0;
|
||||
}
|
||||
|
||||
if (hasCommander()) { // 1 Commander, or 2 Partner Commanders
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package forge.game;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -1434,6 +1435,10 @@ public class GameAction {
|
||||
}
|
||||
|
||||
public void startGame(GameOutcome lastGameOutcome) {
|
||||
startGame(lastGameOutcome, null);
|
||||
}
|
||||
|
||||
public void startGame(GameOutcome lastGameOutcome, Runnable startGameHook) {
|
||||
Player first = determineFirstTurnPlayer(lastGameOutcome);
|
||||
|
||||
GameType gameType = game.getRules().getGameType();
|
||||
@@ -1444,6 +1449,9 @@ public class GameAction {
|
||||
// Where there are none, it should bring up speed controls
|
||||
game.fireEvent(new GameEventGameStarted(gameType, first, game.getPlayers()));
|
||||
|
||||
// Emissary's Plot
|
||||
// runPreOpeningHandActions(first);
|
||||
|
||||
game.setAge(GameStage.Mulligan);
|
||||
for (final Player p1 : game.getPlayers()) {
|
||||
p1.drawCards(p1.getMaxHandSize());
|
||||
@@ -1453,8 +1461,9 @@ public class GameAction {
|
||||
}
|
||||
|
||||
// Choose starting hand for each player with multiple hands
|
||||
|
||||
performMulligans(first, game.getRules().hasAppliedVariant(GameType.Commander));
|
||||
if (game.getRules().getGameType() != GameType.Puzzle) {
|
||||
performMulligans(first, game.getRules().hasAppliedVariant(GameType.Commander));
|
||||
}
|
||||
if (game.isGameOver()) { break; } // conceded during "mulligan" prompt
|
||||
|
||||
game.setAge(GameStage.Play);
|
||||
@@ -1472,8 +1481,8 @@ public class GameAction {
|
||||
game.getTriggerHandler().runTrigger(TriggerType.NewGame, runParams, true);
|
||||
//</THIS CODE WILL WORK WITH PHASE = NULL>
|
||||
|
||||
game.getPhaseHandler().startFirstTurn(first);
|
||||
|
||||
game.getPhaseHandler().startFirstTurn(first, startGameHook);
|
||||
//after game ends, ensure Auto-Pass canceled for all players so it doesn't apply to next game
|
||||
for (Player p : game.getRegisteredPlayers()) {
|
||||
p.getController().autoPassCancel();
|
||||
@@ -1487,15 +1496,20 @@ public class GameAction {
|
||||
// Only cut/coin toss if it's the first game of the match
|
||||
Player goesFirst = null;
|
||||
|
||||
// 904.6: in Archenemy games the Archenemy goes first
|
||||
if (game != null && game.getRules().hasAppliedVariant(GameType.Archenemy)) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
if (p.isArchenemy()) {
|
||||
return p;
|
||||
if (game != null) {
|
||||
if (game.getRules().getGameType().equals(GameType.Puzzle)) {
|
||||
return game.getPlayers().get(0);
|
||||
}
|
||||
|
||||
// 904.6: in Archenemy games the Archenemy goes first
|
||||
if (game.getRules().hasAppliedVariant(GameType.Archenemy)) {
|
||||
for (Player p : game.getPlayers()) {
|
||||
if (p.isArchenemy()) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power Play - Each player with a Power Play in the CommandZone becomes the Starting Player
|
||||
Set<Player> powerPlayers = Sets.newHashSet();
|
||||
for (Card c : game.getCardsIn(ZoneType.Command)) {
|
||||
@@ -1629,6 +1643,31 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void runPreOpeningHandActions(final Player first) {
|
||||
Player takesAction = first;
|
||||
do {
|
||||
//
|
||||
List<Card> ploys = CardLists.filter(takesAction.getCardsIn(ZoneType.Command), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.getName().equals("Emissary's Ploy");
|
||||
}
|
||||
});
|
||||
|
||||
int chosen = 1;
|
||||
List<Integer> cmc = Lists.newArrayList(1, 2, 3);
|
||||
|
||||
for (Card c : ploys) {
|
||||
if (!cmc.isEmpty()) {
|
||||
chosen = takesAction.getController().chooseNumber(c.getSpellPermanent(), "Emissary's Ploy", cmc, c.getOwner());
|
||||
cmc.remove((Object)chosen);
|
||||
}
|
||||
|
||||
c.setChosenNumber(chosen);
|
||||
}
|
||||
takesAction = game.getNextPlayerAfter(takesAction);
|
||||
} while (takesAction != first);
|
||||
}
|
||||
|
||||
private void runOpeningHandActions(final Player first) {
|
||||
Player takesAction = first;
|
||||
|
||||
@@ -354,7 +354,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
|
||||
abstract public void setCounters(final Map<CounterType, Integer> allCounters);
|
||||
|
||||
abstract public boolean canReceiveCounters(final CounterType type);
|
||||
abstract protected void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents);
|
||||
abstract public void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents);
|
||||
abstract public void subtractCounter(final CounterType counterName, final int n);
|
||||
abstract public void clearCounters();
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ public enum GameType {
|
||||
Quest (DeckFormat.QuestDeck, true, true, false, "Quest", ""),
|
||||
QuestDraft (DeckFormat.Limited, true, true, true, "Quest Draft", ""),
|
||||
PlanarConquest (DeckFormat.PlanarConquest, true, false, false, "Planar Conquest", ""),
|
||||
Puzzle (DeckFormat.Puzzle, false, false, false, "Puzzle", "Solve a puzzle from the given game state"),
|
||||
Constructed (DeckFormat.Constructed, false, true, true, "Constructed", ""),
|
||||
DeckManager (DeckFormat.Constructed, false, true, true, "Deck Manager", ""),
|
||||
Vanguard (DeckFormat.Vanguard, true, true, true, "Vanguard", "Each player has a special \"Avatar\" card that affects the game."),
|
||||
|
||||
@@ -80,6 +80,10 @@ public class Match {
|
||||
}
|
||||
|
||||
public void startGame(final Game game) {
|
||||
startGame(game, null);
|
||||
}
|
||||
|
||||
public void startGame(final Game game, Runnable startGameHook) {
|
||||
prepareAllZones(game);
|
||||
if (rules.useAnte()) { // Deciding which cards go to ante
|
||||
Multimap<Player, Card> list = game.chooseCardsForAnte(rules.getMatchAnteRarity());
|
||||
@@ -92,7 +96,8 @@ public class Match {
|
||||
}
|
||||
|
||||
GameOutcome lastOutcome = gamesPlayed.isEmpty() ? null : gamesPlayed.get(gamesPlayed.size() - 1);
|
||||
game.getAction().startGame(lastOutcome);
|
||||
|
||||
game.getAction().startGame(lastOutcome, startGameHook);
|
||||
|
||||
if (rules.useAnte()) {
|
||||
executeAnte(game);
|
||||
|
||||
@@ -987,7 +987,7 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents) {
|
||||
public void addCounter(final CounterType counterType, final int n, final boolean applyMultiplier, final boolean fireEvents) {
|
||||
int addAmount = n;
|
||||
if(addAmount < 0) {
|
||||
addAmount = 0; // As per rule 107.1b
|
||||
|
||||
@@ -878,6 +878,10 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
private final static boolean DEBUG_PHASES = false;
|
||||
|
||||
public void startFirstTurn(Player goesFirst) {
|
||||
startFirstTurn(goesFirst, null);
|
||||
}
|
||||
|
||||
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
if (phase != null) {
|
||||
@@ -891,6 +895,11 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
// don't even offer priority, because it's untap of 1st turn now
|
||||
givePriorityToPlayer = false;
|
||||
|
||||
if (startGameHook != null) {
|
||||
startGameHook.run();
|
||||
givePriorityToPlayer = true;
|
||||
}
|
||||
|
||||
// MAIN GAME LOOP
|
||||
while (!game.isGameOver()) {
|
||||
if (givePriorityToPlayer) {
|
||||
|
||||
@@ -884,7 +884,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCounter(CounterType counterType, int n, boolean applyMultiplier, boolean fireEvents) {
|
||||
public void addCounter(CounterType counterType, int n, boolean applyMultiplier, boolean fireEvents) {
|
||||
if (!canReceiveCounters(counterType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import forge.screens.home.gauntlet.VSubmenuGauntletContests;
|
||||
import forge.screens.home.gauntlet.VSubmenuGauntletLoad;
|
||||
import forge.screens.home.gauntlet.VSubmenuGauntletQuick;
|
||||
import forge.screens.home.online.VSubmenuOnlineLobby;
|
||||
import forge.screens.home.puzzle.VSubmenuPuzzleCreate;
|
||||
import forge.screens.home.puzzle.VSubmenuPuzzleSolve;
|
||||
import forge.screens.home.quest.VSubmenuChallenges;
|
||||
import forge.screens.home.quest.VSubmenuDuels;
|
||||
import forge.screens.home.quest.VSubmenuQuestData;
|
||||
@@ -71,6 +73,8 @@ public enum EDocID {
|
||||
HOME_ACHIEVEMENTS (VSubmenuAchievements.SINGLETON_INSTANCE),
|
||||
HOME_AVATARS (VSubmenuAvatars.SINGLETON_INSTANCE),
|
||||
HOME_UTILITIES (VSubmenuDownloaders.SINGLETON_INSTANCE),
|
||||
HOME_PUZZLE_CREATE(VSubmenuPuzzleCreate.SINGLETON_INSTANCE),
|
||||
HOME_PUZZLE_SOLVE(VSubmenuPuzzleSolve.SINGLETON_INSTANCE),
|
||||
HOME_CONSTRUCTED (VSubmenuConstructed.SINGLETON_INSTANCE),
|
||||
HOME_DRAFT (VSubmenuDraft.SINGLETON_INSTANCE),
|
||||
HOME_SEALED (VSubmenuSealed.SINGLETON_INSTANCE),
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package forge.screens.deckeditor.views;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import forge.gui.framework.DragCell;
|
||||
import forge.gui.framework.DragTab;
|
||||
import forge.gui.framework.EDocID;
|
||||
@@ -11,6 +8,9 @@ import forge.item.InventoryItem;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.ItemManagerContainer;
|
||||
import forge.screens.deckeditor.controllers.CCardCatalog;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* Assembles Swing components of card catalog in deck editor.
|
||||
|
||||
@@ -11,6 +11,7 @@ public enum EMenuGroup {
|
||||
SANCTIONED ("Sanctioned Formats"),
|
||||
ONLINE ("Online Multiplayer"),
|
||||
QUEST ("Quest Mode"),
|
||||
PUZZLE ("Puzzle Mode"),
|
||||
GAUNTLET ("Gauntlets"),
|
||||
SETTINGS ("Game Settings");
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ import javax.swing.JPanel;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import forge.screens.home.puzzle.VSubmenuPuzzleCreate;
|
||||
import forge.screens.home.puzzle.VSubmenuPuzzleSolve;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import forge.Singletons;
|
||||
import forge.assets.FSkinProp;
|
||||
@@ -56,6 +58,7 @@ import forge.screens.home.quest.VSubmenuQuestPrefs;
|
||||
import forge.screens.home.sanctioned.VSubmenuConstructed;
|
||||
import forge.screens.home.sanctioned.VSubmenuDraft;
|
||||
import forge.screens.home.sanctioned.VSubmenuSealed;
|
||||
import forge.screens.home.sanctioned.VSubmenuWinston;
|
||||
import forge.screens.home.settings.VSubmenuAchievements;
|
||||
import forge.screens.home.settings.VSubmenuAvatars;
|
||||
import forge.screens.home.settings.VSubmenuDownloaders;
|
||||
@@ -132,6 +135,9 @@ public enum VHomeUI implements IVTopLevelUI {
|
||||
allSubmenus.add(VSubmenuGauntletLoad.SINGLETON_INSTANCE);
|
||||
allSubmenus.add(VSubmenuGauntletContests.SINGLETON_INSTANCE);
|
||||
|
||||
allSubmenus.add(VSubmenuPuzzleSolve.SINGLETON_INSTANCE);
|
||||
allSubmenus.add(VSubmenuPuzzleCreate.SINGLETON_INSTANCE);
|
||||
|
||||
allSubmenus.add(VSubmenuPreferences.SINGLETON_INSTANCE);
|
||||
allSubmenus.add(VSubmenuAchievements.SINGLETON_INSTANCE);
|
||||
allSubmenus.add(VSubmenuAvatars.SINGLETON_INSTANCE);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package forge.screens.home.puzzle;
|
||||
|
||||
import forge.gui.framework.ICDoc;
|
||||
import forge.menus.IMenuProvider;
|
||||
import forge.menus.MenuUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public enum CSubmenuPuzzleCreate implements ICDoc, IMenuProvider {
|
||||
SINGLETON_INSTANCE;
|
||||
|
||||
private VSubmenuPuzzleCreate view = VSubmenuPuzzleCreate.SINGLETON_INSTANCE;
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
MenuUtil.setMenuProvider(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JMenu> getMenus() {
|
||||
final List<JMenu> menus = new ArrayList<JMenu>();
|
||||
menus.add(PuzzleGameMenu.getMenu());
|
||||
return menus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package forge.screens.home.puzzle;
|
||||
|
||||
import forge.GuiBase;
|
||||
import forge.UiCommand;
|
||||
import forge.deck.Deck;
|
||||
import forge.game.GameRules;
|
||||
import forge.game.GameType;
|
||||
import forge.game.player.RegisteredPlayer;
|
||||
import forge.gauntlet.GauntletData;
|
||||
import forge.gauntlet.GauntletIO;
|
||||
import forge.gui.SOverlayUtils;
|
||||
import forge.gui.framework.ICDoc;
|
||||
import forge.match.HostedMatch;
|
||||
import forge.menus.IMenuProvider;
|
||||
import forge.menus.MenuUtil;
|
||||
import forge.player.GamePlayerUtil;
|
||||
import forge.puzzle.Puzzle;
|
||||
import forge.puzzle.PuzzleIO;
|
||||
import forge.quest.QuestUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public enum CSubmenuPuzzleSolve implements ICDoc, IMenuProvider {
|
||||
SINGLETON_INSTANCE;
|
||||
|
||||
private VSubmenuPuzzleSolve view = VSubmenuPuzzleSolve.SINGLETON_INSTANCE;
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
view.getList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
updateData();
|
||||
view.getBtnStart().addActionListener(
|
||||
new ActionListener() { @Override
|
||||
public void actionPerformed(final ActionEvent e) { startPuzzleSolve(); } });
|
||||
}
|
||||
|
||||
private final UiCommand cmdStart = new UiCommand() {
|
||||
@Override public void run() {
|
||||
startPuzzleSolve();
|
||||
}
|
||||
};
|
||||
|
||||
private void updateData() {
|
||||
final ArrayList<Puzzle> puzzles = PuzzleIO.loadPuzzles();
|
||||
for(Puzzle p : puzzles) {
|
||||
view.getModel().addElement(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
MenuUtil.setMenuProvider(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JMenu> getMenus() {
|
||||
final List<JMenu> menus = new ArrayList<JMenu>();
|
||||
menus.add(PuzzleGameMenu.getMenu());
|
||||
return menus;
|
||||
}
|
||||
|
||||
private boolean startPuzzleSolve() {
|
||||
final Puzzle selected = (Puzzle)view.getList().getSelectedValue();
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SOverlayUtils.startGameOverlay();
|
||||
SOverlayUtils.showOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch();
|
||||
hostedMatch.setStartGameHook(new Runnable() {
|
||||
@Override
|
||||
public final void run() {
|
||||
selected.applyToGame(hostedMatch.getGame());
|
||||
}
|
||||
});
|
||||
|
||||
final List<RegisteredPlayer> players = new ArrayList<RegisteredPlayer>();
|
||||
final RegisteredPlayer human = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.getGuiPlayer());
|
||||
human.setStartingHand(0);
|
||||
players.add(human);
|
||||
|
||||
final RegisteredPlayer ai = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.createAiPlayer());
|
||||
ai.setStartingHand(0);
|
||||
players.add(ai);
|
||||
|
||||
GameRules rules = new GameRules(GameType.Puzzle);
|
||||
rules.setGamesPerMatch(1);
|
||||
hostedMatch.startMatch(rules, null, players, human, GuiBase.getInterface().getNewGuiGame());
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SOverlayUtils.hideOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package forge.screens.home.puzzle;
|
||||
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgePreferences;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
public class PuzzleGameMenu {
|
||||
private PuzzleGameMenu() { }
|
||||
|
||||
private static ForgePreferences prefs = FModel.getPreferences();
|
||||
|
||||
public static JMenu getMenu() {
|
||||
JMenu menu = new JMenu("Puzzle");
|
||||
menu.setMnemonic(KeyEvent.VK_G);
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package forge.screens.home.puzzle;
|
||||
|
||||
import forge.gui.framework.DragCell;
|
||||
import forge.gui.framework.DragTab;
|
||||
import forge.gui.framework.EDocID;
|
||||
import forge.interfaces.IPlayerChangeListener;
|
||||
import forge.match.GameLobby;
|
||||
import forge.match.LocalLobby;
|
||||
import forge.net.event.UpdateLobbyPlayerEvent;
|
||||
import forge.screens.home.EMenuGroup;
|
||||
import forge.screens.home.IVSubmenu;
|
||||
import forge.screens.home.VHomeUI;
|
||||
import forge.screens.home.VLobby;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public enum VSubmenuPuzzleCreate implements IVSubmenu<CSubmenuPuzzleCreate> {
|
||||
SINGLETON_INSTANCE;
|
||||
|
||||
private DragCell parentCell;
|
||||
private final DragTab tab = new DragTab("Puzzle Mode: Create");
|
||||
|
||||
private final GameLobby lobby = new LocalLobby();
|
||||
private final VLobby vLobby = new VLobby(lobby);
|
||||
|
||||
VSubmenuPuzzleCreate() {
|
||||
lobby.setListener(vLobby);
|
||||
|
||||
vLobby.setPlayerChangeListener(new IPlayerChangeListener() {
|
||||
@Override public final void update(final int index, final UpdateLobbyPlayerEvent event) {
|
||||
lobby.applyToSlot(index, event);
|
||||
}
|
||||
});
|
||||
|
||||
vLobby.update(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EMenuGroup getGroupEnum() {
|
||||
return EMenuGroup.PUZZLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
return "Create";
|
||||
}
|
||||
|
||||
@Override
|
||||
public EDocID getItemEnum() {
|
||||
return EDocID.HOME_PUZZLE_CREATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EDocID getDocumentID() {
|
||||
return EDocID.HOME_PUZZLE_CREATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragTab getTabLabel() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CSubmenuPuzzleCreate getLayoutControl() {
|
||||
return CSubmenuPuzzleCreate.SINGLETON_INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentCell(DragCell cell0) {
|
||||
this.parentCell = cell0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragCell getParentCell() {
|
||||
return this.parentCell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate() {
|
||||
final JPanel container = VHomeUI.SINGLETON_INSTANCE.getPnlDisplay();
|
||||
|
||||
container.removeAll();
|
||||
container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right"));
|
||||
vLobby.getLblTitle().setText("Puzzle Mode: Create");
|
||||
container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
|
||||
|
||||
|
||||
if (container.isShowing()) {
|
||||
container.validate();
|
||||
container.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package forge.screens.home.puzzle;
|
||||
|
||||
import forge.gui.framework.DragCell;
|
||||
import forge.gui.framework.DragTab;
|
||||
import forge.gui.framework.EDocID;
|
||||
import forge.interfaces.IPlayerChangeListener;
|
||||
import forge.match.GameLobby;
|
||||
import forge.match.LocalLobby;
|
||||
import forge.net.event.UpdateLobbyPlayerEvent;
|
||||
import forge.screens.home.*;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public enum VSubmenuPuzzleSolve implements IVSubmenu<CSubmenuPuzzleSolve> {
|
||||
SINGLETON_INSTANCE;
|
||||
|
||||
private final JList puzzleList;
|
||||
final DefaultListModel model = new DefaultListModel();
|
||||
|
||||
private final StartButton btnStart = new StartButton();
|
||||
|
||||
private DragCell parentCell;
|
||||
private final DragTab tab = new DragTab("Puzzle Mode: Solve");
|
||||
|
||||
private final GameLobby lobby = new LocalLobby();
|
||||
private final VLobby vLobby = new VLobby(lobby);
|
||||
|
||||
VSubmenuPuzzleSolve() {
|
||||
puzzleList = new JList<>();
|
||||
lobby.setListener(vLobby);
|
||||
|
||||
vLobby.setPlayerChangeListener(new IPlayerChangeListener() {
|
||||
@Override public final void update(final int index, final UpdateLobbyPlayerEvent event) {
|
||||
lobby.applyToSlot(index, event);
|
||||
}
|
||||
});
|
||||
|
||||
vLobby.update(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EMenuGroup getGroupEnum() {
|
||||
return EMenuGroup.PUZZLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMenuTitle() {
|
||||
return "Solve";
|
||||
}
|
||||
|
||||
@Override
|
||||
public EDocID getItemEnum() {
|
||||
return EDocID.HOME_PUZZLE_SOLVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EDocID getDocumentID() {
|
||||
return EDocID.HOME_PUZZLE_SOLVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragTab getTabLabel() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CSubmenuPuzzleSolve getLayoutControl() {
|
||||
return CSubmenuPuzzleSolve.SINGLETON_INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentCell(DragCell cell0) {
|
||||
this.parentCell = cell0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragCell getParentCell() {
|
||||
return this.parentCell;
|
||||
}
|
||||
|
||||
public JList getList() {
|
||||
return puzzleList;
|
||||
}
|
||||
|
||||
public DefaultListModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public StartButton getBtnStart() {
|
||||
return btnStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate() {
|
||||
final JPanel container = VHomeUI.SINGLETON_INSTANCE.getPnlDisplay();
|
||||
|
||||
container.removeAll();
|
||||
container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right"));
|
||||
vLobby.getLblTitle().setText("Puzzle Mode: Solve");
|
||||
container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx");
|
||||
puzzleList.setModel(model);
|
||||
container.add(puzzleList, "w 80%, h 200px!, gap 0 0 0px 0px, span 2, al center");
|
||||
container.add(btnStart, "w 98%!, ax center, gap 1% 0 20px 20px, span 2");
|
||||
|
||||
|
||||
if (container.isShowing()) {
|
||||
container.validate();
|
||||
container.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import forge.properties.ForgePreferences.FPref;
|
||||
import forge.screens.FScreen;
|
||||
import forge.screens.constructed.ConstructedScreen;
|
||||
import forge.screens.gauntlet.NewGauntletScreen;
|
||||
import forge.screens.home.puzzle.PuzzleScreen;
|
||||
import forge.screens.limited.NewDraftScreen;
|
||||
import forge.screens.limited.NewSealedScreen;
|
||||
import forge.screens.planarconquest.NewConquestScreen;
|
||||
@@ -24,6 +25,7 @@ public class NewGameMenu extends FPopupMenu {
|
||||
BoosterDraft("Booster Draft", FSkinImage.HAND, NewDraftScreen.class),
|
||||
SealedDeck("Sealed Deck", FSkinImage.PACK, NewSealedScreen.class),
|
||||
QuestMode("Quest Mode", FSkinImage.QUEST_ZEP, NewQuestScreen.class),
|
||||
PuzzleMode("Puzzle Mode", FSkinImage.QUEST_BOOK, PuzzleScreen.class),
|
||||
PlanarConquest("Planar Conquest", FSkinImage.MULTIVERSE, NewConquestScreen.class),
|
||||
Gauntlet("Gauntlet", FSkinImage.ALPHASTRIKE, NewGauntletScreen.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package forge.screens.home.puzzle;
|
||||
|
||||
import forge.assets.FSkinFont;
|
||||
import forge.screens.LaunchScreen;
|
||||
import forge.screens.home.NewGameMenu;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FTextArea;
|
||||
import forge.util.ThreadUtil;
|
||||
import forge.util.Utils;
|
||||
|
||||
public class PuzzleScreen extends LaunchScreen {
|
||||
private static final float PADDING = Utils.scale(10);
|
||||
|
||||
private final FTextArea lblDesc = add(new FTextArea(false,
|
||||
"Puzzle Mode loads in a puzzle that you have to win in a predetermined time/way."));
|
||||
|
||||
public PuzzleScreen() {
|
||||
super(null, NewGameMenu.getMenu());
|
||||
|
||||
lblDesc.setFont(FSkinFont.get(12));
|
||||
lblDesc.setTextColor(FLabel.INLINE_LABEL_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLayoutAboveBtnStart(float startY, float width, float height) {
|
||||
float x = PADDING;
|
||||
float y = startY + PADDING;
|
||||
float w = width - 2 * PADDING;
|
||||
float h = height - y - PADDING;
|
||||
lblDesc.setBounds(x, y, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startMatch() {
|
||||
ThreadUtil.invokeInGameThread(new Runnable() { //must run in game thread to prevent blocking UI thread
|
||||
@Override
|
||||
public void run() {
|
||||
// Load selected puzzle
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
13
forge-gui/res/puzzle/PS1.pzl
Normal file
13
forge-gui/res/puzzle/PS1.pzl
Normal file
@@ -0,0 +1,13 @@
|
||||
[metadata]
|
||||
Name:Possibility Storm #1
|
||||
URL:https://i.redd.it/wws1h03gy7ky.png
|
||||
Goal:Win
|
||||
Turns:1
|
||||
[state]
|
||||
ActivePlayer=Human
|
||||
ActivePhase=Main1
|
||||
HumanLife=20
|
||||
AILife=9
|
||||
HumanPlay=Key to the City; Ravenous Intruder; Ruinous Gremlin; Island; Island; Island; Island; Island; Mountain; Mountain
|
||||
HumanHand=Welcome to the Fold; Chilling Grasp; Flame Lash; Confirm Suspicions
|
||||
AIPlay=Workshop Assistant; Thriving Ibex
|
||||
@@ -54,6 +54,7 @@ public class HostedMatch {
|
||||
private Match match;
|
||||
private Game game;
|
||||
private String title;
|
||||
private Runnable startGameHook = null;
|
||||
private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList();
|
||||
private Map<RegisteredPlayer, IGuiGame> guis;
|
||||
private int humanCount;
|
||||
@@ -62,7 +63,10 @@ public class HostedMatch {
|
||||
private final Map<PlayerControllerHuman, NextGameDecision> nextGameDecisions = Maps.newHashMap();
|
||||
private boolean isMatchOver = false;
|
||||
|
||||
public HostedMatch() {
|
||||
public HostedMatch() {}
|
||||
|
||||
public void setStartGameHook(Runnable hook) {
|
||||
startGameHook = hook;
|
||||
}
|
||||
|
||||
private static GameRules getDefaultRules(final GameType gameType) {
|
||||
@@ -216,9 +220,8 @@ public class HostedMatch {
|
||||
playbackControl.setGame(game);
|
||||
game.subscribeToEvents(playbackControl);
|
||||
}
|
||||
|
||||
// Actually start the game!
|
||||
match.startGame(game);
|
||||
match.startGame(game, startGameHook);
|
||||
|
||||
// After game is over...
|
||||
isMatchOver = match.isMatchOver();
|
||||
|
||||
@@ -62,6 +62,8 @@ public final class ForgeConstants {
|
||||
public static final String MUSIC_DIR = RES_DIR + "music" + PATH_SEPARATOR;
|
||||
public static final String LANG_DIR = RES_DIR + "languages" + PATH_SEPARATOR;
|
||||
public static final String EFFECTS_DIR = RES_DIR + "effects" + PATH_SEPARATOR;
|
||||
public static final String PUZZLE_DIR = RES_DIR + "puzzle" + PATH_SEPARATOR;
|
||||
|
||||
|
||||
private static final String QUEST_DIR = RES_DIR + "quest" + PATH_SEPARATOR;
|
||||
public static final String QUEST_WORLD_DIR = QUEST_DIR + "world" + PATH_SEPARATOR;
|
||||
|
||||
@@ -108,6 +108,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
|
||||
SUBMENU_ONLINE ("false"),
|
||||
SUBMENU_GAUNTLET ("false"),
|
||||
SUBMENU_QUEST ("false"),
|
||||
SUBMENU_PUZZLE("false"),
|
||||
SUBMENU_SETTINGS ("false"),
|
||||
SUBMENU_UTILITIES ("false"),
|
||||
|
||||
|
||||
104
forge-gui/src/main/java/forge/puzzle/Puzzle.java
Normal file
104
forge-gui/src/main/java/forge/puzzle/Puzzle.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package forge.puzzle;
|
||||
|
||||
import forge.ai.GameState;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.model.FModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Puzzle extends GameState implements InventoryItem {
|
||||
String name;
|
||||
String goal;
|
||||
String url;
|
||||
int turns;
|
||||
|
||||
public Puzzle(Map<String, List<String>> puzzleLines) {
|
||||
loadMetaData(puzzleLines.get("metadata"));
|
||||
loadGameState(puzzleLines.get("state"));
|
||||
// Generate goal enforcement
|
||||
}
|
||||
|
||||
private void loadMetaData(List<String> metadataLines) {
|
||||
for(String line : metadataLines) {
|
||||
String[] split = line.split(":");
|
||||
if ("Name".equalsIgnoreCase(split[0])) {
|
||||
this.name = split[1];
|
||||
} else if ("Goal".equalsIgnoreCase(split[0])) {
|
||||
this.goal = split[1];
|
||||
} else if ("Url".equalsIgnoreCase(split[0])) {
|
||||
this.url = split[1];
|
||||
} else if ("Turns".equalsIgnoreCase(split[0])) {
|
||||
this.turns = Integer.parseInt(split[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGameState(List<String> stateLines) {
|
||||
this.parse(stateLines);
|
||||
}
|
||||
|
||||
public IPaperCard getPaperCard(final String cardName) {
|
||||
return FModel.getMagicDb().getCommonCards().getCard(cardName);
|
||||
}
|
||||
|
||||
public void addGoalEnforcement(Game game) {
|
||||
Player human = null;
|
||||
for(Player p : game.getPlayers()) {
|
||||
if (p.getController().isGuiPlayer()) {
|
||||
human = p;
|
||||
}
|
||||
}
|
||||
|
||||
Card goalCard = new Card(-1, game);
|
||||
|
||||
goalCard.setOwner(human);
|
||||
goalCard.setImageKey("t:puzzle");
|
||||
goalCard.setName("Puzzle Goal");
|
||||
goalCard.addType("Effect");
|
||||
|
||||
{
|
||||
final String loseTrig = "Mode$ Phase | Phase$ Cleanup | TriggerZones$ Command | Static$ True | " +
|
||||
"ValidPlayer$ You | TriggerDescription$ At the beginning of your cleanup step, you lose the game.";
|
||||
final String loseEff = "DB$ LosesGame | Defined$ You";
|
||||
|
||||
final Trigger loseTrigger = TriggerHandler.parseTrigger(loseTrig, goalCard, true);
|
||||
|
||||
loseTrigger.setOverridingAbility(AbilityFactory.getAbility(loseEff, goalCard));
|
||||
goalCard.addTrigger(loseTrigger);
|
||||
}
|
||||
human.getZone(ZoneType.Command).add(goalCard);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyGameOnThread(final Game game) {
|
||||
super.applyGameOnThread(game);
|
||||
addGoalEnforcement(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType() {
|
||||
return "Puzzle";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageKey(boolean altState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String toString() { return name; }
|
||||
}
|
||||
50
forge-gui/src/main/java/forge/puzzle/PuzzleIO.java
Normal file
50
forge-gui/src/main/java/forge/puzzle/PuzzleIO.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package forge.puzzle;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.util.FileSection;
|
||||
import forge.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PuzzleIO {
|
||||
|
||||
public static final String TXF_PROMPT = "[New Puzzle]";
|
||||
public static final String SUFFIX_DATA = ".pzl";
|
||||
|
||||
public static ArrayList<Puzzle> loadPuzzles() {
|
||||
String[] pList;
|
||||
// get list of custom draft files
|
||||
final File pFolder = new File(ForgeConstants.PUZZLE_DIR);
|
||||
if (!pFolder.exists()) {
|
||||
throw new RuntimeException("Puzzles : folder not found -- folder is " + pFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (!pFolder.isDirectory()) {
|
||||
throw new RuntimeException("Puzzles : not a folder -- " + pFolder.getAbsolutePath());
|
||||
}
|
||||
|
||||
pList = pFolder.list();
|
||||
|
||||
ArrayList<Puzzle> puzzles = Lists.newArrayList();
|
||||
for (final String element : pList) {
|
||||
if (element.endsWith(SUFFIX_DATA)) {
|
||||
final List<String> pfData = FileUtil.readFile(ForgeConstants.PUZZLE_DIR + element);
|
||||
puzzles.add(new Puzzle(parsePuzzleSections(pfData)));
|
||||
}
|
||||
}
|
||||
return puzzles;
|
||||
}
|
||||
|
||||
public static final Map<String, List<String>> parsePuzzleSections(List<String> pfData) {
|
||||
return FileSection.parseSections(pfData);
|
||||
}
|
||||
|
||||
|
||||
public static File getPuzzleFile(final String name) {
|
||||
return new File(ForgeConstants.PUZZLE_DIR, name + SUFFIX_DATA);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user