Merge branch 'master' of git.cardforge.org:core-developers/forge into agetian-master

This commit is contained in:
Agetian
2018-02-20 07:55:13 +03:00
10 changed files with 86 additions and 44 deletions

View File

@@ -186,6 +186,11 @@ public class DestroyAi extends SpellAbilityAi {
if (hasXCost) { if (hasXCost) {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement. // TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa)); maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
// X can't be more than the lands we have in our hand for "discard X lands"!
if ("ScorchedEarth".equals(logic)) {
int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
maxTargets = Math.min(maxTargets, lands);
}
} }
if (sa.hasParam("AIMaxTgtsCount")) { if (sa.hasParam("AIMaxTgtsCount")) {
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified // Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified

View File

@@ -295,9 +295,10 @@ public class CardFactory {
sa.setRightSplit(); sa.setRightSplit();
} }
} }
CardFactoryUtil.setupKeywordedAbilities(card);
final CardState original = card.getState(CardStateName.Original); final CardState original = card.getState(CardStateName.Original);
original.addNonManaAbilities(card.getCurrentState().getNonManaAbilities()); original.addNonManaAbilities(card.getCurrentState().getNonManaAbilities());
original.addIntrinsicKeywords(card.getCurrentState().getIntrinsicKeywordStrings(), false); // Copy 'Fuse' to original side original.addIntrinsicKeywords(card.getCurrentState().getIntrinsicKeywords()); // Copy 'Fuse' to original side
original.getSVars().putAll(card.getCurrentState().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute) original.getSVars().putAll(card.getCurrentState().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute)
} else if (state != CardStateName.Original){ } else if (state != CardStateName.Original){
CardFactoryUtil.setupKeywordedAbilities(card); CardFactoryUtil.setupKeywordedAbilities(card);
@@ -305,6 +306,10 @@ public class CardFactory {
} }
card.setState(CardStateName.Original, false); card.setState(CardStateName.Original, false);
// need to update keyword cache for original spell
if (card.isSplitCard()) {
card.updateKeywordsCache(card.getCurrentState());
}
// ****************************************************************** // ******************************************************************
// ************** Link to different CardFactories ******************* // ************** Link to different CardFactories *******************

View File

@@ -2999,7 +2999,7 @@ public class CardFactoryUtil {
public static void addReplacementEffect(final KeywordInterface inst, final Card card, final boolean intrinsic) { public static void addReplacementEffect(final KeywordInterface inst, final Card card, final boolean intrinsic) {
String keyword = inst.getOriginal(); String keyword = inst.getOriginal();
if (keyword.equals("Aftermath")) { if (keyword.equals("Aftermath") && card.getCurrentStateName().equals(CardStateName.RightSplit)) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile "); sb.append("Event$ Moved | ValidCard$ Card.Self | Origin$ Stack | ExcludeDestination$ Exile ");
sb.append("| ValidStackSa$ Spell.Aftermath | Description$ Aftermath"); sb.append("| ValidStackSa$ Spell.Aftermath | Description$ Aftermath");
@@ -3021,8 +3021,6 @@ public class CardFactoryUtil {
re.setOverridingAbility(saExile); re.setOverridingAbility(saExile);
// Aftermath only on Rightsplit
// doesn't make a copy with it
inst.addReplacement(re); inst.addReplacement(re);
} else if (keyword.startsWith("Amplify")) { } else if (keyword.startsWith("Amplify")) {
final String[] ampString = keyword.split(":"); final String[] ampString = keyword.split(":");
@@ -3493,11 +3491,11 @@ public class CardFactoryUtil {
inst.addSpellAbility(newSA); inst.addSpellAbility(newSA);
} }
} else if (keyword.equals("Aftermath")) { } else if (keyword.equals("Aftermath") && card.getCurrentStateName().equals(CardStateName.RightSplit)) {
// Aftermath does modify existing SA, and does not add new one // Aftermath does modify existing SA, and does not add new one
// only target RightSplit of it // only target RightSplit of it
final SpellAbility origSA = card.getState(CardStateName.RightSplit).getFirstAbility(); final SpellAbility origSA = card.getFirstSpellAbility();
origSA.setAftermath(true); origSA.setAftermath(true);
origSA.getRestrictions().setZone(ZoneType.Graveyard); origSA.getRestrictions().setZone(ZoneType.Graveyard);
// The Exile part is done by the System itself // The Exile part is done by the System itself
@@ -3742,9 +3740,9 @@ public class CardFactoryUtil {
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Fuse")) { } else if (keyword.startsWith("Fuse") && card.getCurrentStateName().equals(CardStateName.Original)) {
final SpellAbility sa = AbilityFactory.buildFusedAbility(card); final SpellAbility sa = AbilityFactory.buildFusedAbility(card);
card.getState(CardStateName.Original).addNonManaAbility(sa); card.addSpellAbility(sa);
sa.setTemporary(!intrinsic); sa.setTemporary(!intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);

View File

@@ -164,9 +164,6 @@ public class CardState extends GameObject {
public final Collection<KeywordInterface> getIntrinsicKeywords() { public final Collection<KeywordInterface> getIntrinsicKeywords() {
return intrinsicKeywords.getValues(); return intrinsicKeywords.getValues();
} }
public final Iterable<String> getIntrinsicKeywordStrings() {
return intrinsicKeywords;
}
public final boolean hasIntrinsicKeyword(String k) { public final boolean hasIntrinsicKeyword(String k) {
return intrinsicKeywords.contains(k); return intrinsicKeywords.contains(k);
} }

View File

@@ -3,5 +3,6 @@ ManaCost:W
Types:Enchantment Types:Enchantment
A:AB$ PumpAll | Cost$ 2 W | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ Creatures you control get +1/+1 until end of turn. A:AB$ PumpAll | Cost$ 2 W | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ Creatures you control get +1/+1 until end of turn.
SVar:NonStackingEffect:True SVar:NonStackingEffect:True
SVar:PlayMain1:TRUE
SVar:Picture:http://www.wizards.com/global/images/magic/general/gerrards_battle_cry.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/gerrards_battle_cry.jpg
Oracle:{2}{W}: Creatures you control get +1/+1 until end of turn. Oracle:{2}{W}: Creatures you control get +1/+1 until end of turn.

View File

@@ -1,10 +1,11 @@
Name:Scorched Earth Name:Scorched Earth
ManaCost:X R ManaCost:X R
Types:Sorcery Types:Sorcery
A:SP$ Destroy | Cost$ X R Discard<X/Land> | TargetMin$ 0 | TargetMax$ MaxTgts | ValidTgts$ Land | TgtPrompt$ Select target land | References$ X | SpellDescription$ Destroy X target lands. A:SP$ Destroy | Cost$ X R Discard<X/Land> | TargetMin$ 0 | TargetMax$ MaxTgts | ValidTgts$ Land | TgtPrompt$ Select target land | References$ X | SpellDescription$ Destroy X target lands. | AILogic$ ScorchedEarth
# It may seem wrong to not use X in the target, but since the Targets are what defines X, it's redundant (and not supported by the code) # It may seem wrong to not use X in the target, but since the Targets are what defines X, it's redundant (and not supported by the code)
SVar:X:Targeted$Amount SVar:X:Targeted$Amount
SVar:MaxTgts:Count$Valid Land SVar:MaxTgts:Count$Valid Land
SVar:RemAIDeck:True SVar:RemRandomDeck:True
SVar:PlayBeforeLandDrop:true
SVar:Picture:http://www.wizards.com/global/images/magic/general/scorched_earth.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/scorched_earth.jpg
Oracle:As an additional cost to cast Scorched Earth, discard X land cards.\nDestroy X target lands. Oracle:As an additional cost to cast Scorched Earth, discard X land cards.\nDestroy X target lands.

View File

@@ -4,10 +4,12 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -49,7 +51,7 @@ public abstract class GameLobby implements IHasGameType {
private final boolean allowNetworking; private final boolean allowNetworking;
private HostedMatch hostedMatch; private HostedMatch hostedMatch;
private final Map<LobbySlot, IGameController> gameControllers = Maps.newHashMap(); private final HashMap<LobbySlot, IGameController> gameControllers = Maps.newHashMap();
protected GameLobby(final boolean allowNetworking) { protected GameLobby(final boolean allowNetworking) {
this.allowNetworking = allowNetworking; this.allowNetworking = allowNetworking;
} }
@@ -481,6 +483,8 @@ public abstract class GameLobby implements IHasGameType {
} }
} }
hostedMatch.gameControllers = gameControllers;
onGameStarted(); onGameStarted();
} }
}; };

View File

@@ -3,11 +3,14 @@ package forge.match;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import forge.LobbyPlayer;
import forge.interfaces.IGameController;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -55,6 +58,7 @@ public class HostedMatch {
private Match match; private Match match;
private Game game; private Game game;
private String title; private String title;
public HashMap<LobbySlot, IGameController> gameControllers = null;
private Runnable startGameHook = null; private Runnable startGameHook = null;
private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList(); private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList();
private Map<RegisteredPlayer, IGuiGame> guis; private Map<RegisteredPlayer, IGuiGame> guis;
@@ -180,6 +184,12 @@ public class HostedMatch {
game.subscribeToEvents(new FControlGameEventHandler(humanController)); game.subscribeToEvents(new FControlGameEventHandler(humanController));
playersPerGui.add(gui, p.getView()); playersPerGui.add(gui, p.getView());
if (gameControllers != null ) {
LobbySlot lobbySlot = getLobbySlot(p.getLobbyPlayer());
gameControllers.put(lobbySlot, humanController);
}
humanControllers.add(humanController); humanControllers.add(humanController);
humanCount++; humanCount++;
} }
@@ -238,6 +248,18 @@ public class HostedMatch {
}); });
} }
private LobbySlot getLobbySlot(LobbyPlayer lobbyPlayer) {
for (LobbySlot key: gameControllers.keySet()) {
IGameController value = gameControllers.get(key);
if (value instanceof PlayerControllerHuman) {
if (lobbyPlayer == ((PlayerControllerHuman) value).getLobbyPlayer()) {
return key;
}
}
}
return null;
}
public void registerSpectator(final IGuiGame gui) { public void registerSpectator(final IGuiGame gui) {
final PlayerControllerHuman humanController = new WatchLocalGame(game, null, gui); final PlayerControllerHuman humanController = new WatchLocalGame(game, null, gui);
gui.setSpectator(humanController); gui.setSpectator(humanController);

View File

@@ -27,6 +27,8 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
private final FGameClient client; private final FGameClient client;
private final IGuiGame gui; private final IGuiGame gui;
private Tracker tracker; private Tracker tracker;
private Match match;
private Game game;
/** /**
* Creates a client-side game handler. * Creates a client-side game handler.
@@ -36,6 +38,8 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
this.client = client; this.client = client;
this.gui = client.getGui(); this.gui = client.getGui();
this.tracker = null; this.tracker = null;
this.match = null;
this.game = null;
} }
@Override @Override
@@ -58,32 +62,26 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) { protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) {
switch (protocolMethod) { switch (protocolMethod) {
case openView: case openView:
if (this.tracker == null) { // only need one **match**
int maxAttempts = 5; if (this.match == null) {
for (int numAttempts = 0; numAttempts < maxAttempts; numAttempts++) { this.match = createMatch();
try { }
this.tracker = createTracker(); // openView is called **once** per game, for now create a new Game instance each time
this.game = createGame();
for (PlayerView myPlayer : (TrackableCollection<PlayerView>) args[0]) { // get a tracker
if (myPlayer.getTracker() == null) { this.tracker = createTracker();
myPlayer.setTracker(this.tracker);
}
}
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0]; for (PlayerView myPlayer : (TrackableCollection<PlayerView>) args[0]) {
client.setGameControllers(myPlayers); if (myPlayer.getTracker() == null) {
myPlayer.setTracker(this.tracker);
} catch (Exception e) {
System.err.println("Failed: attempt number: " + numAttempts + " - " + e.toString());
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
} }
} }
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
client.setGameControllers(myPlayers);
break; break;
default: default:
break; break;
@@ -108,19 +106,17 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
} }
/** /**
* This method creates the necessary objects and state to retrieve a <b>Tracker</b> object. * This method retrieves enough of the existing (incomplete) game state to
* * recreate a new viable Match object
* Near as I can tell, that means that we need to create a <b>Match</b>.
* *
* Creating a <b>Match</b> requires that we have: * Creating a <b>Match</b> requires that we have:
* * <b>GameRules</b> * * <b>GameRules</b>
* * <b>RegisteredPlayers</b> * * <b>RegisteredPlayers</b>
* * Title * * Title
* *
* @return Tracker * @return Match
*/ */
private Tracker createTracker() { private Match createMatch() {
// retrieve what we can from the existing (but incomplete) state // retrieve what we can from the existing (but incomplete) state
final IGuiGame gui = client.getGui(); final IGuiGame gui = client.getGui();
GameView gameView = gui.getGameView(); GameView gameView = gui.getGameView();
@@ -134,12 +130,24 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
// create a valid match object and game // create a valid match object and game
Match match = new Match(gameRules, registeredPlayers, title); Match match = new Match(gameRules, registeredPlayers, title);
Game game = match.createGame();
return match;
}
private Game createGame() {
this.tracker = null;
return this.match.createGame();
}
/**
* Ensure the stored GameView is correct and retrieve a <b>Tracker</b> object.
*
* @return Tracker
*/
private Tracker createTracker() {
// replace the existing incomplete GameView with the newly created one // replace the existing incomplete GameView with the newly created one
gui.setGameView(null); gui.setGameView(null);
gui.setGameView(game.getView()); gui.setGameView(game.getView());
return gui.getGameView().getTracker(); return gui.getGameView().getTracker();
} }

View File

@@ -55,6 +55,7 @@ import com.google.common.collect.Maps;
public final class FServerManager { public final class FServerManager {
private static FServerManager instance = null; private static FServerManager instance = null;
private byte[] externalAddress = new byte[]{8,8,8,8};
private boolean isHosting = false; private boolean isHosting = false;
private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); private final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
private final EventLoopGroup workerGroup = new NioEventLoopGroup(); private final EventLoopGroup workerGroup = new NioEventLoopGroup();
@@ -210,7 +211,7 @@ public final class FServerManager {
// https://stackoverflow.com/a/901943 // https://stackoverflow.com/a/901943
private String getRoutableAddress(boolean preferIpv4, boolean preferIPv6) throws SocketException, UnknownHostException { private String getRoutableAddress(boolean preferIpv4, boolean preferIPv6) throws SocketException, UnknownHostException {
DatagramSocket s = new DatagramSocket(); DatagramSocket s = new DatagramSocket();
s.connect(InetAddress.getByAddress(new byte[]{8,8,8,8}), 0); s.connect(InetAddress.getByAddress(this.externalAddress), 0);
NetworkInterface n = NetworkInterface.getByInetAddress(s.getLocalAddress()); NetworkInterface n = NetworkInterface.getByInetAddress(s.getLocalAddress());
Enumeration en = n.getInetAddresses(); Enumeration en = n.getInetAddresses();
while (en.hasMoreElements()) { while (en.hasMoreElements()) {