mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
Merge branch 'companion' into 'master'
Companion See merge request core-developers/forge!2714
This commit is contained in:
@@ -183,7 +183,7 @@ public class Match {
|
||||
return myRemovedAnteCards;
|
||||
}
|
||||
|
||||
private static void preparePlayerLibrary(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil) {
|
||||
private static void preparePlayerZone(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil) {
|
||||
PlayerZone library = player.getZone(zoneType);
|
||||
List<Card> newLibrary = new ArrayList<>();
|
||||
for (final Entry<PaperCard, Integer> stackOfCards : section) {
|
||||
@@ -234,9 +234,9 @@ public class Match {
|
||||
for (int i = 0; i < playersConditions.size(); i++) {
|
||||
final Player player = players.get(i);
|
||||
final RegisteredPlayer psc = playersConditions.get(i);
|
||||
PlayerController person = player.getController();
|
||||
|
||||
if (canSideBoard) {
|
||||
PlayerController person = player.getController();
|
||||
if (sideboardProxy != null && person.isAI()) {
|
||||
person = sideboardProxy;
|
||||
}
|
||||
@@ -270,16 +270,26 @@ public class Match {
|
||||
}
|
||||
}
|
||||
|
||||
preparePlayerLibrary(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
||||
preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
|
||||
if (myDeck.has(DeckSection.Sideboard)) {
|
||||
preparePlayerLibrary(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||
preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
|
||||
|
||||
// Assign Companion
|
||||
Card companion = player.assignCompanion(game, person);
|
||||
// Create an effect that lets you cast your companion from your sideboard
|
||||
if (companion != null) {
|
||||
PlayerZone commandZone = player.getZone(ZoneType.Command);
|
||||
companion = game.getAction().moveTo(ZoneType.Command, companion, null);
|
||||
commandZone.add(Player.createCompanionEffect(game, companion));
|
||||
|
||||
player.updateZoneForView(commandZone);
|
||||
}
|
||||
}
|
||||
|
||||
player.initVariantsZones(psc);
|
||||
|
||||
player.shuffle(null);
|
||||
|
||||
|
||||
if (isFirstGame) {
|
||||
Collection<? extends PaperCard> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
|
||||
if (null != cardsComplained) {
|
||||
|
||||
@@ -40,10 +40,7 @@ import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostSacrifice;
|
||||
import forge.game.event.*;
|
||||
import forge.game.event.GameEventCardDamaged.DamageType;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordCollection;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.keyword.KeywordsChange;
|
||||
import forge.game.keyword.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.replacement.ReplaceMoved;
|
||||
@@ -1904,6 +1901,9 @@ public class Card extends GameEntity implements Comparable<Card> {
|
||||
String desc = "(As this Saga enters and after your draw step, "
|
||||
+ " add a lore counter. Sacrifice after " + Strings.repeat("I", Integer.valueOf(k[1])) + ".)";
|
||||
sbLong.append(desc);
|
||||
} else if (inst.getKeyword().equals(Keyword.COMPANION)) {
|
||||
sbLong.append("Companion - ");
|
||||
sbLong.append(((Companion)inst).getDescription());
|
||||
}
|
||||
else {
|
||||
if ((i != 0) && (sb.length() != 0)) {
|
||||
|
||||
44
forge-game/src/main/java/forge/game/keyword/Companion.java
Normal file
44
forge-game/src/main/java/forge/game/keyword/Companion.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package forge.game.keyword;
|
||||
|
||||
public class Companion extends SimpleKeyword {
|
||||
|
||||
private String deckRestriction = null;
|
||||
private String description = null;
|
||||
private String specialRules = null;
|
||||
|
||||
public Companion() { }
|
||||
|
||||
@Override
|
||||
protected void parse(String details) {
|
||||
String[] splitString = details.split(":");
|
||||
int descriptionIndex = splitString.length - 1;
|
||||
|
||||
if (splitString.length < 2) {
|
||||
System.out.println("Did not parse a long enough value for Companion.");
|
||||
return;
|
||||
}
|
||||
|
||||
deckRestriction = splitString[0];
|
||||
|
||||
if (deckRestriction.equals("Special")) {
|
||||
specialRules = splitString[1];
|
||||
}
|
||||
description = splitString[descriptionIndex];
|
||||
}
|
||||
|
||||
public String getDeckRestriction() {
|
||||
return deckRestriction;
|
||||
}
|
||||
|
||||
public boolean hasSpecialRestriction() {
|
||||
return specialRules != null;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getSpecialRules() {
|
||||
return specialRules;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ public enum Keyword {
|
||||
CHAMPION("Champion", KeywordWithType.class, false, "When this enters the battlefield, sacrifice it unless you exile another %s you control. When this leaves the battlefield, that card returns to the battlefield."),
|
||||
CHANGELING("Changeling", SimpleKeyword.class, true, "This card is every creature type."),
|
||||
CIPHER("Cipher", SimpleKeyword.class, true, "Then you may exile this spell card encoded on a creature you control. Whenever that creature deals combat damage to a player, its controller may cast a copy of the encoded card without paying its mana cost."),
|
||||
COMPANION("Companion", Companion.class, true, "Reveal your companion from outside the game if your deck meets the companion restriction."),
|
||||
CONSPIRE("Conspire", SimpleKeyword.class, false, "As an additional cost to cast this spell, you may tap two untapped creatures you control that each share a color with it. If you do, copy it."),
|
||||
CONVOKE("Convoke", SimpleKeyword.class, true, "Your creatures can help cast this spell. Each creature you tap while playing this spell reduces its cost by {1} or by one mana of that creature's color."),
|
||||
CREW("Crew", KeywordWithAmount.class, false, "Tap any number of creatures you control with total power %1$d or more: This Vehicle becomes an artifact creature until end of turn."),
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.google.common.collect.*;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.LobbyPlayer;
|
||||
import forge.card.CardType;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
@@ -33,11 +34,8 @@ import forge.game.ability.effects.DetachedCardEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.event.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordCollection;
|
||||
import forge.game.keyword.*;
|
||||
import forge.game.keyword.KeywordCollection.KeywordCollectionView;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
import forge.game.keyword.KeywordsChange;
|
||||
import forge.game.mana.ManaPool;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -1510,6 +1508,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
cl.addAll(getZone(ZoneType.Library).getCardsPlayerCanActivate(this));
|
||||
if (includeCommandZone) {
|
||||
cl.addAll(getZone(ZoneType.Command).getCardsPlayerCanActivate(this));
|
||||
cl.addAll(getZone(ZoneType.Sideboard).getCardsPlayerCanActivate(this));
|
||||
}
|
||||
|
||||
//External activatables from all opponents
|
||||
@@ -2852,6 +2851,93 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
}
|
||||
|
||||
public Card assignCompanion(Game game, PlayerController player) {
|
||||
List<Card> legalCompanions = Lists.newArrayList();
|
||||
|
||||
boolean uniqueNames = true;
|
||||
Set<String> cardNames = new HashSet<>();
|
||||
Set<CardType.CoreType> cardTypes = new HashSet<>();
|
||||
for (final Card c : getCardsIn(ZoneType.Library)) {
|
||||
if (uniqueNames) {
|
||||
if (cardNames.contains(c.getName())) {
|
||||
uniqueNames = false;
|
||||
} else {
|
||||
cardNames.add(c.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!c.isLand()) {
|
||||
for(CardType.CoreType type : c.getPaperCard().getRules().getType().getCoreTypes()) {
|
||||
cardTypes.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int deckSize = getCardsIn(ZoneType.Library).size();
|
||||
int minSize = game.getMatch().getRules().getGameType().getDeckFormat().getMainRange().getMinimum();
|
||||
|
||||
for (final Card c : getCardsIn(ZoneType.Sideboard)) {
|
||||
for (KeywordInterface inst : c.getKeywords()) {
|
||||
if (!(inst instanceof Companion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Companion kwInstance = (Companion) inst;
|
||||
if (kwInstance.hasSpecialRestriction()) {
|
||||
String specialRules = kwInstance.getSpecialRules();
|
||||
if (specialRules.equals("UniqueNames")) {
|
||||
if (uniqueNames) {
|
||||
legalCompanions.add(c);
|
||||
}
|
||||
} else if (specialRules.equals("DeckSizePlus20")) {
|
||||
// +20 deck size to min deck size
|
||||
if (deckSize >= minSize + 20) {
|
||||
legalCompanions.add(c);
|
||||
}
|
||||
} else if (specialRules.equals("SharesCardType")) {
|
||||
// Shares card type
|
||||
if (cardTypes.size() == 1) {
|
||||
legalCompanions.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
} else{
|
||||
String restriction = kwInstance.getDeckRestriction();
|
||||
if (deckMatchesDeckRestriction(c, restriction)) {
|
||||
legalCompanions.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (legalCompanions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CardCollectionView view = CardCollection.getView(legalCompanions);
|
||||
|
||||
return controller.chooseSingleEntityForEffect(view, null, "Choose a companion", true);
|
||||
}
|
||||
|
||||
public boolean deckMatchesDeckRestriction(Card source, String restriction) {
|
||||
for (final Card c : getCardsIn(ZoneType.Library)) {
|
||||
if (!c.isValid(restriction, this, source, null)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static DetachedCardEffect createCompanionEffect(Game game, Card companion) {
|
||||
final String name = Lang.getPossesive(companion.getName()) + " Companion Effect";
|
||||
DetachedCardEffect eff = new DetachedCardEffect(companion, name);
|
||||
|
||||
String mayBePlayedAbility = "Mode$ Continuous | EffectZone$ Command | MayPlay$ True | Affected$ Card.YouOwn+EffectSource | AffectedZone$ Command";
|
||||
eff.addStaticAbility(mayBePlayedAbility);
|
||||
// Probably remove this effect when the spell is cast via a static trigger
|
||||
return eff;
|
||||
}
|
||||
|
||||
public static DetachedCardEffect createCommanderEffect(Game game, Card commander) {
|
||||
final String name = Lang.getPossesive(commander.getName()) + " Commander Effect";
|
||||
DetachedCardEffect eff = new DetachedCardEffect(commander, name);
|
||||
|
||||
@@ -464,6 +464,7 @@ public class PlayerView extends GameEntityView {
|
||||
|
||||
//update flashback zone when graveyard, library, or exile zones updated
|
||||
switch (zone.getZoneType()) {
|
||||
case Command:
|
||||
case Graveyard:
|
||||
case Library:
|
||||
case Exile:
|
||||
|
||||
11
forge-gui/res/cardsfolder/upcoming/gyruda_doom_of_depths.txt
Normal file
11
forge-gui/res/cardsfolder/upcoming/gyruda_doom_of_depths.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
Name:Gyruda, Doom of Depths
|
||||
ManaCost:4 U/B U/B
|
||||
Types:Legendary Creature Demon Kraken
|
||||
PT:6/6
|
||||
K:Companion:Card.cmcM20:Your starting deck contains only cards with even converted mana costs. (If this card is your chosen companion, you may cast it once from outside the game.)
|
||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, each player puts the top four cards of their library into their graveyard. Put a creature card with an even converted mana cost from those cards onto the battlefield under your control.
|
||||
SVar:TrigMill:DB$ Mill | NumCards$ 4 | Defined$ Player | RememberMilled$ True | SubAbility$ DBChoose
|
||||
SVar:DBChoose:DB$ ChooseCard | Defined$ You | ChoiceTitle$ Choose a creature card with an even converted mana cost | Choices$ Card.Creature+IsRemembered+cmcM20 | References$ X | ChoiceZone$ Graveyard | AILogic$ BestCard | Mandatory$ True | SubAbility$ DBChangeZone
|
||||
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ ChosenCard | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
Oracle:Companion — Your starting deck contains only cards with even converted mana costs. (If this card is your chosen companion, you may cast it once from outside the game.)\nWhen Gyruda, Doom of Depths enters the battlefield, each player puts the top four cards of their library into their graveyard. Put a creature card with an even converted mana cost from among those cards onto the battlefield under your control.
|
||||
Reference in New Issue
Block a user