Merged changes from trunk to GuiRefactoring: 27266-27293; plus some minor fixes.

This commit is contained in:
elcnesh
2014-09-12 11:07:11 +00:00
parent 1ba2cb498b
commit c3e9ff7e5b
25 changed files with 487 additions and 31 deletions

1
.gitattributes vendored
View File

@@ -16749,6 +16749,7 @@ forge-gui/src/main/java/forge/match/input/InputSelectTargets.java -text
forge-gui/src/main/java/forge/match/input/InputSynchronized.java -text
forge-gui/src/main/java/forge/match/input/InputSyncronizedBase.java -text
forge-gui/src/main/java/forge/match/input/package-info.java -text
forge-gui/src/main/java/forge/model/Achievement.java -text
forge-gui/src/main/java/forge/model/CardBlock.java -text
forge-gui/src/main/java/forge/model/CardCollections.java -text
forge-gui/src/main/java/forge/model/FModel.java svneol=native#text/plain

View File

@@ -51,7 +51,7 @@ public class AiCardMemory {
*/
public enum MemorySet {
MANDATORY_ATTACKERS,
HELD_MANA_SOURCES, // stub, not linked to AI code yet
HELD_MANA_SOURCES,
//REVEALED_CARDS // stub, not linked to AI code yet
}

View File

@@ -68,6 +68,7 @@ import forge.game.replacement.ReplaceMoved;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell;
@@ -740,6 +741,7 @@ public class AiController {
&& !ComputerUtil.castPermanentInMain1(player, sa)) {
return AiPlayDecision.WaitForMain2;
}
// save cards with flash for surprise blocking
if (card.hasKeyword("Flash")
&& (player.isUnlimitedHandSize() || player.getCardsIn(ZoneType.Hand).size() <= player.getMaxHandSize())
@@ -751,6 +753,20 @@ public class AiController {
return AiPlayDecision.AnotherTime;
}
// don't play cards without being able to pay the upkeep for
for (String ability : card.getKeyword()) {
if (ability.startsWith("At the beginning of your upkeep, sacrifice CARDNAME unless you pay")) {
final String[] k = ability.split(" pay ");
final String costs = k[1].replaceAll("[{]", "").replaceAll("[}]", " ");
Cost cost = new Cost(costs, true);
final Ability emptyAbility = new AbilityStatic(card, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
emptyAbility.setActivatingPlayer(player);
if (!ComputerUtilCost.canPayCost(emptyAbility, player)) {
return AiPlayDecision.AnotherTime;
}
}
}
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
} else if( sa instanceof Spell ) {
return canPlaySpellBasic(card);
@@ -759,7 +775,7 @@ public class AiController {
}
private AiPlayDecision canPlaySpellBasic(final Card card) {
if (card.getSVar("NeedsToPlay").length() > 0) {
if (card.hasSVar("NeedsToPlay")) {
final String needsToPlay = card.getSVar("NeedsToPlay");
List<Card> list = game.getCardsIn(ZoneType.Battlefield);

View File

@@ -29,7 +29,8 @@ public enum AiProps { /** */
DEFAULT_PLANAR_DIE_ROLL_CHANCE ("50"), /** */
MULLIGAN_THRESHOLD ("5"), /** */
PLANAR_DIE_ROLL_HESITATION_CHANCE ("10"),
CHEAT_WITH_MANA_ON_SHUFFLE ("FALSE"); /** */
CHEAT_WITH_MANA_ON_SHUFFLE ("false"),
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("always"); /** */
private final String strDefaultVal;

View File

@@ -985,8 +985,8 @@ public class AttachAi extends SpellAbilityAi {
// TODO AttachSource is currently set for the Source of the Spell, but
// at some point can support attaching a different card
// Don't equip if already equipping
if (attachSource.getEquippingCard() != null && attachSource.getEquippingCard().getController() == aiPlayer || attachSource.hasSVar("DontEquip")) {
// Don't equip if DontEquip SVar is set
if (attachSource.hasSVar("DontEquip")) {
return null;
}
// Don't fortify if already fortifying
@@ -1017,6 +1017,35 @@ public class AttachAi extends SpellAbilityAi {
Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic"));
AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi();
if (c != null && attachSource.getType().contains("Equipment")
&& attachSource.getEquippingCard() != null
&& attachSource.getEquippingCard().getController() == aiPlayer) {
if (c.equals(attachSource.getEquippingCard())) {
// Do not equip if equipping the same card already
return null;
}
if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("never")) {
// Do not equip other creatures if the AI profile does not allow moving equipment around
return null;
} else if (aic.getProperty(AiProps.MOVE_EQUIPMENT_TO_BETTER_CREATURES).equals("from_useless_only")) {
// Do not equip other creatures if the AI profile only allows moving equipment from useless creatures
// and the equipped creature is still useful (not non-untapping+tapped and not set to can't attack/block)
if (!isUselessCreature(aiPlayer, attachSource.getEquippingCard())) {
return null;
}
}
// make sure to prioritize casting spells in main 2 (creatures, other equipment, etc.) rather than moving equipment around
if (aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Attach);
if (futureSpell != null && futureSpell.getHostCard() != null) {
aic.reserveManaSourcesForMain2(futureSpell);
}
}
}
if ((c == null) && mandatory) {
CardLists.shuffle(list);
c = list.get(0);
@@ -1086,7 +1115,7 @@ public class AttachAi extends SpellAbilityAi {
}
// Consider exceptional cases which break the normal evaluation rules
if (!isUsefulAttachAction(c, sa)) {
if (!isUsefulAttachAction(ai, c, sa)) {
return null;
}
@@ -1279,7 +1308,7 @@ public class AttachAi extends SpellAbilityAi {
* @param sa SpellAbility
* @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility)
*/
private static boolean isUsefulAttachAction(Card c, SpellAbility sa) {
private static boolean isUsefulAttachAction(Player ai, Card c, SpellAbility sa) {
if (c == null) {
return false;
}
@@ -1292,17 +1321,28 @@ public class AttachAi extends SpellAbilityAi {
ArrayList<String> cardTypes = sa.getHostCard().getType();
if (cardTypes.contains("Equipment") && c.hasKeyword("CARDNAME can't attack or block.")) {
if (cardTypes.contains("Equipment") && isUselessCreature(ai, c)) {
// useless to equip a creature that can't attack or block.
return false;
} else if (cardTypes.contains("Equipment") && c.isTapped() && c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
// useless to equip a creature that won't untap and is tapped.
return false;
}
return true;
}
private static boolean isUselessCreature(Player ai, Card c) {
if (c == null) {
return true;
}
if (c.hasKeyword("CARDNAME can't attack or block.")
|| (c.hasKeyword("CARDNAME doesn't untap during your untap step.") && c.isTapped())
|| (c.getOwner() == ai && ai.getOpponents().contains(c.getController()))) {
return true;
}
return false;
}
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return true;

View File

@@ -32,6 +32,7 @@ import forge.error.BugReportDialog;
import forge.events.UiEvent;
import forge.game.GameType;
import forge.game.Match;
import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.IHasIcon;
import forge.game.player.RegisteredPlayer;
@@ -152,17 +153,30 @@ public class GuiDesktop implements IGuiBase {
@Override
public <T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions) {
if (initialInput instanceof Card || (inputOptions != null && inputOptions.length > 0 && inputOptions[0] instanceof Card)) {
System.err.println("Warning: Cards passed to GUI! Printing stack trace.");
Thread.dumpStack();
}
return FOptionPane.showInputDialog(message, title, icon == null ? null : FSkin.getImage(icon), initialInput, inputOptions);
}
@Override
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) {
if ((choices != null && !choices.isEmpty() && choices.iterator().next() instanceof Card) || selected instanceof Card) {
System.err.println("Warning: Cards passed to GUI! Printing stack trace.");
Thread.dumpStack();
}
return GuiChoose.getChoices(message, min, max, choices, selected, display);
}
@Override
public <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
final List<T> sourceChoices, final List<T> destChoices, final CardView referenceCard, final boolean sideboardingMode) {
if ((sourceChoices != null && !sourceChoices.isEmpty() && sourceChoices.iterator().next() instanceof Card)
|| (destChoices != null && !destChoices.isEmpty() && destChoices.iterator().next() instanceof Card)) {
System.err.println("Warning: Cards passed to GUI! Printing stack trace.");
Thread.dumpStack();
}
return GuiChoose.order(title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode);
}

View File

@@ -105,6 +105,11 @@ public enum FSkinImage implements FImage {
ARCSON (FSkinProp.ICO_ARCSON, SourceFile.ICONS),
ARCSHOVER (FSkinProp.ICO_ARCSHOVER, SourceFile.ICONS),
//Achievement Trophies
BRONZE_TROPHY (FSkinProp.IMG_BRONZE_TROPHY, SourceFile.ICONS),
SILVER_TROPHY (FSkinProp.IMG_SILVER_TROPHY, SourceFile.ICONS),
GOLD_TROPHY (FSkinProp.IMG_GOLD_TROPHY, SourceFile.ICONS),
//Quest Icons
QUEST_ZEP (FSkinProp.ICO_QUEST_ZEP, SourceFile.ICONS),
QUEST_GEAR (FSkinProp.ICO_QUEST_GEAR, SourceFile.ICONS),

View File

@@ -170,12 +170,15 @@ public class MatchScreen extends FScreen {
if (defender instanceof CardView) {
TargetingOverlay.drawArrow(g, attacker, (CardView) defender);
}
final Iterable<CardView> blockers = combat.getBlockers(attacker);
if (blockers != null) {
//connect each blocker with the attacker it's blocking
for (final CardView blocker : combat.getBlockers(attacker)) {
TargetingOverlay.drawArrow(g, blocker, attacker);
}
}
}
}
if (activeEffect != null) {
activeEffect.draw(g, 10, 10, 100, 100);

View File

@@ -8,7 +8,6 @@ import forge.Graphics;
import forge.assets.FSkinFont;
import forge.assets.FImage;
import forge.assets.FSkinImage;
import forge.assets.ImageUtil;
import forge.card.CardRenderer;
import forge.card.CardZoom;
import forge.screens.match.views.VPrompt;
@@ -246,11 +245,7 @@ public class FOptionPane extends FDialog {
float promptHeight = 0;
if (lblIcon != null) {
float maxLabelWidth = Utils.AVG_FINGER_WIDTH * 0.8f;
float labelWidth = ImageUtil.getNearestHQSize(maxLabelWidth, lblIcon.getIcon().getWidth());
if (labelWidth > maxLabelWidth) {
labelWidth /= 2;
}
float labelWidth = Utils.scaleX(lblIcon.getIcon().getWidth());
promptHeight = lblIcon.getIcon().getHeight() * labelWidth / lblIcon.getIcon().getWidth();
if (promptHeight > maxPromptHeight) {
promptHeight = maxPromptHeight;

View File

@@ -27,6 +27,15 @@ This also applies to spells with Replicate and Multikicker to allow picking the
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
When triggered abilities have only one valid target, that target will now be auto-selected.
- AI improvements -
Some artificial intelligence improvements were made in this version of Forge.
The AI will now always attack with creatures that it has temporarily gained control of until end of turn in order not to miss the opportunity and thus waste the gain control spell.
The AI will no longer bounce guild lands back to hand right after playing them, which sometimes caused the AI to lock itself on land drops completely.
The AI will now try to predict if it wants to cast a spell in Main 2 and reserve some mana for it instead of aggressively pumping creatures with all available mana.
The AI will now also consider pumping a creature if it can predict that this creature will deal more damage to the opponent with its increased power, which should eliminate the senseless attacks with 0/1 creatures that do not get pumped even when they do not get blocked.
The AI will no longer waste equipment on cards that are useless (e.g. are tapped and do not normally untap, or can't attack or block anymore, etc.).
The AI can now optionally move equipment from one creature to another. For this purpose, a new AI profile variable was added: MOVE_EQUIPMENT_TO_BETTER_CREATURES. It defines whether the AI will always move equipment to better creatures if it has mana ('always'), will only move if the currently equipped creature becomes useless as defined above or because the AI loses control of the creature ('from_useless_only'), or will never move equipment around ('never'). The "Default" profile is set to only move the equipment to other creatures if the currently equipped creatures become useless for the AI, while the "Reckless" AI profile always moves equipment to better creatures when given a chance to (if it has mana and if it doesn't need to reserve mana for a future spell in Main 2).
- New Commander 2014 and Khans of Tarkir cards -
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
@@ -64,6 +73,38 @@ Mardu Skullhunter
Heir of the Wilds
Ivorytusk Fortress
Sagu Mauler
Ghostfire Blade
Abzan Ascendancy
Flying Crane Technique
Temur Charger
Utter End
Clever Impersonator
End Hostilities
Ugin's Nexus
Anafenza, the Foremost
Crater's Claws
Hardened Scales
See the Unwritten
Sarkhan, the Dragonspeaker
Sorin, Solemn Visitor
Frontier Bivouac
Mystic Monastery
Nomad Outpost
Opulent Palace
Sandsteppe Citadel
Dragon's Eye Savants
Horde Ambusher
Ruthless Ripper
Watcher of the Roost
Abzan Guide
Jeering Instigator
Ankle Shanker
Jeskai Windscout
Mantis Rider
Murderous Cut
Rakshasa Deathdealer
Savage Knuckleblade
Shambling Attendants
------------

View File

@@ -4,3 +4,4 @@ DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE=3
DEFAULT_PLANAR_DIE_ROLL_CHANCE=50
MULLIGAN_THRESHOLD=5
PLANAR_DIE_ROLL_HESITATION_CHANCE=10
MOVE_EQUIPMENT_TO_BETTER_CREATURES=from_useless_only

View File

@@ -2,5 +2,6 @@ CHEAT_WITH_MANA_ON_SHUFFLE=true
DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN=1
DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE=1
DEFAULT_PLANAR_DIE_ROLL_CHANCE=100
MULLIGAN_THRESHOLD=2
MULLIGAN_THRESHOLD=3
PLANAR_DIE_ROLL_HESITATION_CHANCE=0
MOVE_EQUIPMENT_TO_BETTER_CREATURES=always

View File

@@ -3,6 +3,7 @@ ManaCost:4 B
Types:Enchantment
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ 3 | AddToughness$ 1 | AddKeyword$ Lifelink | CheckSVar$ X | SVarCompare$ EQ1 | Description$ As long as you control exactly one creature, that creature gets +3/+1 and has lifelink.
SVar:X:Count$Valid Creature.YouCtrl
SVar:RemAIDeck:True
SVar:PlayMain1:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/homicidal_seclusion.jpg
Oracle:As long as you control exactly one creature, that creature gets +3/+1 and has lifelink.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

After

Width:  |  Height:  |  Size: 652 KiB

View File

@@ -231,6 +231,11 @@ public enum FSkinProp {
IMG_PACK (new int[] {80, 760, 40, 40}, PropType.IMAGE),
IMG_SORCERY (new int[] {160, 720, 40, 40}, PropType.IMAGE),
//achievement trophies
IMG_BRONZE_TROPHY (new int[] {0, 880, 60, 80}, PropType.IMAGE),
IMG_SILVER_TROPHY (new int[] {60, 880, 60, 80}, PropType.IMAGE),
IMG_GOLD_TROPHY (new int[] {120, 880, 60, 80}, PropType.IMAGE),
//button images
IMG_BTN_START_UP (new int[] {480, 200, 160, 80}, PropType.ICON),
IMG_BTN_START_OVER (new int[] {480, 280, 160, 80}, PropType.ICON),

View File

@@ -53,6 +53,7 @@ import forge.interfaces.IGuiBase;
import forge.match.input.ButtonUtil;
import forge.match.input.InputBase;
import forge.model.FModel;
import forge.player.LobbyPlayerHuman;
import forge.properties.ForgePreferences.FPref;
import forge.util.Lang;
import forge.util.gui.SGuiChoose;
@@ -177,6 +178,9 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
gui.showPromptMessage(""); //clear prompt behind WinLose overlay
ButtonUtil.update(gui, "", "", false, false, false);
gui.finishGame();
if (gui.getGuiPlayer() instanceof LobbyPlayerHuman) {
gameView.updateAchievements((LobbyPlayerHuman) gui.getGuiPlayer());
}
}
});
return null;

View File

@@ -0,0 +1,275 @@
package forge.model;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import forge.assets.FSkinProp;
import forge.game.Game;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.interfaces.IGuiBase;
import forge.properties.ForgeConstants;
import forge.util.XmlUtil;
import forge.util.gui.SOptionPane;
public enum Achievement {
WinStreak("Win Streak", true,
"Win 10 games in a row.", 10,
"Win 25 games in a row.", 25,
"Win 50 games in a row.", 50,
new Evaluator() {
@Override
public int evaluate(Player player, Game game, int current) {
if (player.getOutcome().hasWon()) {
return current + 1;
}
return 0; //reset if player didn't win
}
}),
TotalWins("Total Wins", true,
"Win 100 games.", 100,
"Win 250 games.", 250,
"Win 500 games.", 500,
new Evaluator() {
@Override
public int evaluate(Player player, Game game, int current) {
if (player.getOutcome().hasWon()) {
return current + 1;
}
return current;
}
}),
Overkill("Overkill", true,
"Win game with opponent at -25 life or less.", 25,
"Win game with opponent at -50 life or less.", 50,
"Win game with opponent at -100 life or less.", 100,
new Evaluator() {
@Override
public int evaluate(Player player, Game game, int current) {
if (player.getOutcome().hasWon()) {
Player opponent = getSingleOpponent(player);
if (opponent != null && opponent.getLife() < 0) {
return -opponent.getLife();
}
}
return 0;
}
}),
LifeToSpare("Life to Spare", true,
"Win game with 20 life more than you started with.", 20,
"Win game with 40 life more than you started with.", 40,
"Win game with 80 life more than you started with.", 80,
new Evaluator() {
@Override
public int evaluate(Player player, Game game, int current) {
if (player.getOutcome().hasWon()) {
int gainedLife = player.getLife() - player.getStartingLife();
if (gainedLife > 0) {
return gainedLife;
}
}
return 0;
}
}),
Hellbent("Hellbent", false,
"Win game with no cards in hand.", 1,
"Win game with no cards in hand or library.", 2,
"Win game with no cards in hand, library, or graveyard.", 3,
new Evaluator() {
@Override
public int evaluate(Player player, Game game, int current) {
if (player.getOutcome().hasWon()) {
if (player.getZone(ZoneType.Hand).size() == 0) {
if (player.getZone(ZoneType.Library).size() == 0) {
if (player.getZone(ZoneType.Graveyard).size() == 0) {
return 3;
}
return 2;
}
return 1;
}
}
return 0;
}
});
private final String displayName, bronzeDesc, silverDesc, goldDesc;
private final int bronzeThreshold, silverThreshold, goldThreshold;
private final boolean showBest;
private final Evaluator evaluator;
private int best, current;
private Achievement(String displayName0, boolean showBest0,
String bronzeDesc0, int bronzeThreshold0,
String silverDesc0, int silverThreshold0,
String goldDesc0, int goldThreshold0,
Evaluator evaluator0) {
displayName = displayName0;
showBest = showBest0;
bronzeDesc = bronzeDesc0;
bronzeThreshold = bronzeThreshold0;
silverDesc = silverDesc0;
silverThreshold = silverThreshold0;
goldDesc = goldDesc0;
goldThreshold = goldThreshold0;
evaluator = evaluator0;
}
public String getDisplayName() {
return displayName;
}
public String getBronzeDesc() {
return bronzeDesc;
}
public String getSilverDesc() {
return silverDesc;
}
public String getGoldDesc() {
return goldDesc;
}
public int getBest() {
return best;
}
public boolean showBest() {
return showBest;
}
public boolean earnedGold() {
return best >= goldThreshold;
}
public boolean earnedSilver() {
return best >= silverThreshold;
}
public boolean earnedBronze() {
return best >= bronzeThreshold;
}
public static void updateAll(IGuiBase gui, Player player) {
for (Achievement achievement : Achievement.values()) {
achievement.update(gui, player, false);
}
save();
}
public void update(IGuiBase gui, Player player) {
update(gui, player, true);
}
private void update(IGuiBase gui, Player player, boolean save) {
current = evaluator.evaluate(player, player.getGame(), current);
if (current > best) {
int oldBest = best;
best = current;
String type = null;
FSkinProp image = null;
String desc = null;
if (earnedGold()) {
if (oldBest < goldThreshold) {
type = "Gold";
image = FSkinProp.IMG_GOLD_TROPHY;
desc = goldDesc;
}
}
else if (earnedSilver()) {
if (oldBest < silverThreshold) {
type = "Silver";
image = FSkinProp.IMG_SILVER_TROPHY;
desc = silverDesc;
}
}
else if (earnedBronze()) {
if (oldBest < bronzeThreshold) {
type = "Bronze";
image = FSkinProp.IMG_BRONZE_TROPHY;
desc = bronzeDesc;
}
}
if (type != null) {
SOptionPane.showMessageDialog(gui, "You've earned a " + type + " trophy!\n\n" +
displayName + " - " + desc, "Achievement Earned", image);
}
}
if (save) {
save();
}
}
public static void load() {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final Document document = builder.parse(ForgeConstants.ACHIEVEMENTS_FILE);
final NodeList cards = document.getElementsByTagName("a");
for (int i = 0; i < cards.getLength(); i++) {
final Element el = (Element)cards.item(i);
final Achievement achievement = Achievement.valueOf(el.getAttribute("name"));
achievement.best = getIntAttribute(el, "best");
achievement.current = getIntAttribute(el, "current");
}
}
catch (FileNotFoundException e) {
//ok if file not found
}
catch (MalformedURLException e) {
//ok if file not found
}
catch (Exception e) {
e.printStackTrace();
}
}
private static int getIntAttribute(Element el, String name) {
String value = el.getAttribute(name);
if (value.length() > 0) {
try {
return Integer.parseInt(value);
}
catch (Exception ex) {}
}
return 0;
}
private static void save() {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.newDocument();
Element root = document.createElement("achievements");
document.appendChild(root);
for (Achievement achievement : Achievement.values()) {
if (achievement.best > 0 || achievement.current > 0) {
Element a = document.createElement("a");
a.setAttribute("name", achievement.name());
a.setAttribute("best", String.valueOf(achievement.best));
a.setAttribute("current", String.valueOf(achievement.current));
root.appendChild(a);
}
}
XmlUtil.saveDocument(document, ForgeConstants.ACHIEVEMENTS_FILE);
}
catch (Exception e) {
e.printStackTrace();
}
}
private static abstract class Evaluator {
public abstract int evaluate(Player player, Game game, int current);
//get single opponent of player
protected Player getSingleOpponent(Player player) {
if (player.getGame().getRegisteredPlayers().size() == 2) {
for (Player p : player.getGame().getRegisteredPlayers()) {
if (p.isOpponentOf(player)) {
return p;
}
}
}
return null;
}
}
}

View File

@@ -155,6 +155,7 @@ public class FModel {
CardPreferences.load();
DeckPreferences.load();
ItemManagerConfig.load();
Achievement.load();
//preload AI profiles
AiProfileUtil.loadAllProfiles(ForgeConstants.AI_PROFILE_DIR);

View File

@@ -38,4 +38,8 @@ public class LobbyPlayerHuman extends LobbyPlayer implements IGameEntitiesFactor
public void hear(LobbyPlayer player, String message) {
gui.hear(player, message);
}
public IGuiBase getGui() {
return this.gui;
}
}

View File

@@ -508,6 +508,7 @@ public class PlayerControllerHuman extends PlayerController {
List<Card> toBottom = null;
List<Card> toTop = null;
mayLookAt.addAll(topN);
if (topN.size() == 1) {
if (willPutCardOnTop(topN.get(0))) {
toTop = topN;
@@ -517,7 +518,8 @@ public class PlayerControllerHuman extends PlayerController {
}
}
else {
toBottom = SGuiChoose.many(getGui(), "Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null);
final List<CardView> toBottomViews = SGuiChoose.many(getGui(), "Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, gameView.getCardViews(topN), null);
toBottom = gameView.getCards(toBottomViews);
topN.removeAll(toBottom);
if (topN.isEmpty()) {
toTop = null;
@@ -526,9 +528,11 @@ public class PlayerControllerHuman extends PlayerController {
toTop = topN;
}
else {
toTop = SGuiChoose.order(getGui(), "Arrange cards to be put on top of your library", "Cards arranged", topN, null);
final List<CardView> toTopViews = SGuiChoose.order(getGui(), "Arrange cards to be put on top of your library", "Cards arranged", gameView.getCardViews(topN), null);
toTop = gameView.getCards(toTopViews);
}
}
mayLookAt.clear();
return ImmutablePair.of(toTop, toBottom);
}
@@ -565,8 +569,10 @@ public class PlayerControllerHuman extends PlayerController {
@Override
public List<Card> chooseCardsToDiscardFrom(Player p, SpellAbility sa, List<Card> valid, int min, int max) {
if (p != player) {
mayLookAt.addAll(valid);
final List<CardView> choices = SGuiChoose.many(getGui(), "Choose " + min + " card" + (min != 1 ? "s" : "") + " to discard",
"Discarded", min, min, gameView.getCardViews(valid), null);
mayLookAt.clear();
return getCards(choices);
}

View File

@@ -98,6 +98,7 @@ public final class ForgeConstants {
DECK_PREFS_FILE = USER_PREFS_DIR + "deck.preferences";
QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences";
ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences";
ACHIEVEMENTS_FILE = USER_PREFS_DIR + "achievements.xml";
_DEFAULTS_DIR = RES_DIR + "defaults/";
NO_CARD_FILE = _DEFAULTS_DIR + "no_card.jpg";
@@ -228,6 +229,7 @@ public final class ForgeConstants {
public static String DECK_PREFS_FILE;
public static String QUEST_PREFS_FILE;
public static String ITEM_VIEW_PREFS_FILE;
public static String ACHIEVEMENTS_FILE;
// data that has defaults in the program dir but overrides/additions in the user dir
private static String _DEFAULTS_DIR;

View File

@@ -50,6 +50,12 @@ public class CombatView {
}
return false;
}
/**
* @param attacker
* @return the blockers associated with an attacker, or {@code null} if the
* attacker is unblocked.
*/
public Iterable<CardView> getBlockers(final CardView attacker) {
return attackersWithBlockers.get(attacker);
}

View File

@@ -10,6 +10,7 @@ import forge.game.GameOutcome;
import forge.game.GameType;
import forge.game.phase.PhaseType;
import forge.game.player.RegisteredPlayer;
import forge.player.LobbyPlayerHuman;
import forge.util.ITriggerEvent;
public interface IGameView {
@@ -36,6 +37,7 @@ public interface IGameView {
public abstract GameOutcome.AnteResult getAnteResult();
public abstract boolean isGameOver();
public abstract void updateAchievements(LobbyPlayerHuman player);
public abstract int getPoisonCountersToLose();

View File

@@ -26,7 +26,10 @@ import forge.game.player.RegisteredPlayer;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
import forge.model.Achievement;
import forge.player.LobbyPlayerHuman;
import forge.util.ITriggerEvent;
import forge.util.ThreadUtil;
public class LocalGameView implements IGameView {
@@ -139,6 +142,24 @@ public class LocalGameView implements IGameView {
return game.isGameOver();
}
@Override
public void updateAchievements(final LobbyPlayerHuman player) {
//update all achievements for GUI player after game finished
ThreadUtil.invokeInGameThread(new Runnable() {
@Override
public void run() {
if (game == null) {
return;
}
for (final Player p : game.getRegisteredPlayers()) {
if (p.getController().getLobbyPlayer() == player) {
Achievement.updateAll(player.getGui(), p);
}
}
}
});
}
@Override
public int getPoisonCountersToLose() {
return game.getRules().getPoisonCountersToLose();

View File

@@ -311,6 +311,17 @@ public class PlayerView extends GameEntityView {
}
}
public int getNCards(final ZoneType zone) {
switch (zone) {
case Hand:
return getnHandCards();
case Library:
return getnLibraryCards();
default:
return getCards(zone).size();
}
}
/**
* @return the nHandCards
*/