Merge pull request #1902 from Northmoc/spaceb

UNF: space_beleren.txt and support
This commit is contained in:
Anthony Calosa
2022-11-22 08:54:01 +08:00
committed by GitHub
24 changed files with 189 additions and 14 deletions

View File

@@ -1,11 +1,7 @@
package forge.ai;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import forge.game.keyword.Keyword;
import org.apache.commons.lang3.StringUtils;
@@ -591,6 +587,11 @@ public class PlayerControllerAi extends PlayerController {
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
}
@Override
public String chooseSector(Card assignee, String ai, List<String> sectors) {
return Aggregates.random(sectors);
}
@Override
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
return !ComputerUtil.wantMulligan(player, cardsToReturn);

View File

@@ -47,6 +47,7 @@ public enum SpellApiToAi {
.put(ApiType.ChooseEvenOdd, ChooseEvenOddAi.class)
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
.put(ApiType.ChooseSector, AlwaysPlayAi.class)
.put(ApiType.ChooseSource, ChooseSourceAi.class)
.put(ApiType.ChooseType, ChooseTypeAi.class)
.put(ApiType.Clash, ClashAi.class)

View File

@@ -37,6 +37,7 @@ import forge.game.mulligan.MulliganService;
import forge.game.player.GameLossReason;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementResult;
@@ -1317,7 +1318,11 @@ public class GameAction {
CardCollection desCreats = null;
CardCollection unAttachList = new CardCollection();
CardCollection sacrificeList = new CardCollection();
PlayerCollection spaceSculptors = new PlayerCollection();
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
if (c.hasKeyword(Keyword.SPACE_SCULPTOR)) {
spaceSculptors.add(c.getController());
}
if (c.isCreature()) {
// Rule 704.5f - Put into grave (no regeneration) for toughness <= 0
if (c.getNetToughness() <= 0) {
@@ -1393,6 +1398,9 @@ public class GameAction {
}
for (Player p : game.getPlayers()) {
if (!spaceSculptors.isEmpty() && !spaceSculptors.contains(p)) {
checkAgain |= stateBasedAction704_5u(p);
}
if (handleLegendRule(p, noRegCreats)) {
checkAgain = true;
}
@@ -1412,6 +1420,11 @@ public class GameAction {
checkAgain = true;
}
}
if (!spaceSculptors.isEmpty()) {
for (Player p : spaceSculptors) {
checkAgain |= stateBasedAction704_5u(p);
}
}
// 704.5m World rule
checkAgain |= handleWorldRule(noRegCreats);
// only check static abilities once after destroying all the creatures
@@ -1551,6 +1564,37 @@ public class GameAction {
return checkAgain;
}
private boolean stateBasedAction704_5u(Player p) {
boolean checkAgain = false;
CardCollection toAssign = new CardCollection();
for (final Card c : p.getCreaturesInPlay().threadSafeIterable()) {
if (!c.hasSector()) {
toAssign.add(c);
if (!checkAgain) {
checkAgain = true;
}
}
}
final StringBuilder sb = new StringBuilder();
for (Card assignee : toAssign) { // probably would be nice for players to pick order of assigning?
String sector = p.getController().chooseSector(assignee, "Assign");
assignee.assignSector(sector);
if (sb.length() == 0) {
sb.append(p).append(" ").append(Localizer.getInstance().getMessage("lblAssigns")).append("\n");
}
String creature = CardTranslation.getTranslatedName(assignee.getName()) + " (" + assignee.getId() + ")";
sb.append(creature).append(" ").append(sector).append("\n");
}
if (sb.length() > 0) {
notifyOfValue(null, p, sb.toString(), p);
}
return checkAgain;
}
private boolean stateBasedAction903_9a(Card c) {
if (c.isRealCommander() && c.canMoveToCommandZone()) {
// FIXME: need to flush the tracker to make sure the Commander is properly updated
@@ -1938,9 +1982,11 @@ public class GameAction {
/** Delivers a message to all players. (use reveal to show Cards) */
public void notifyOfValue(SpellAbility saSource, GameObject relatedTarget, String value, Player playerExcept) {
String name = CardTranslation.getTranslatedName(saSource.getHostCard().getName());
value = TextUtil.fastReplace(value, "CARDNAME", name);
value = TextUtil.fastReplace(value, "NICKNAME", Lang.getInstance().getNickName(name));
if (saSource != null) {
String name = CardTranslation.getTranslatedName(saSource.getHostCard().getName());
value = TextUtil.fastReplace(value, "CARDNAME", name);
value = TextUtil.fastReplace(value, "NICKNAME", Lang.getInstance().getNickName(name));
}
for (Player p : game.getPlayers()) {
if (playerExcept == p) continue;
p.getController().notifyOfValue(saSource, relatedTarget, value);

View File

@@ -44,6 +44,7 @@ public enum ApiType {
ChooseEvenOdd (ChooseEvenOddEffect.class),
ChooseNumber (ChooseNumberEffect.class),
ChoosePlayer (ChoosePlayerEffect.class),
ChooseSector (ChooseSectorEffect.class),
ChooseSource (ChooseSourceEffect.class),
ChooseType (ChooseTypeEffect.class),
Clash (ClashEffect.class),

View File

@@ -0,0 +1,15 @@
package forge.game.ability.effects;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
public class ChooseSectorEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
final String chosen = card.getController().getController().chooseSector(null, sa.getParamOrDefault("AILogic", ""));
card.setChosenSector(chosen);
}
}

View File

@@ -290,6 +290,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
private Direction chosenDirection = null;
private String chosenMode = "";
private String currentRoom = null;
private String sector = null;
private String chosenSector = null;
private Card exiledWith;
private Player exiledBy;
@@ -1898,6 +1900,23 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return false;
}
public String getSector() {
return sector;
}
public void assignSector(String s) {
sector = s;
view.updateSector(this);
}
public boolean hasSector() {
return sector != null;
}
public String getChosenSector() {
return chosenSector;
}
public final void setChosenSector(final String s) {
chosenSector = s;
}
// used for cards like Meddling Mage...
public final String getNamedCard() {
return getChosenName();
@@ -2183,7 +2202,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|| keyword.equals("Ascend") || keyword.equals("Totem armor")
|| keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot")
|| keyword.equals("Daybound") || keyword.equals("Nightbound")
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")) {
|| keyword.equals("Friends forever") || keyword.equals("Choose a Background")
|| keyword.equals("Space sculptor")) {
sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")");
} else if (keyword.startsWith("Partner:")) {
final String[] k = keyword.split(":");

View File

@@ -120,6 +120,14 @@ public class CardProperty {
if (source.hasChosenCard(card)) {
return false;
}
} else if (property.equals("ChosenSector")) {
if (!source.getChosenSector().equals(card.getSector())) {
return false;
}
} else if (property.equals("DifferentSector")) {
if (source.getSector().equals(card.getSector())) {
return false;
}
} else if (property.equals("DoubleFaced")) {
if (!card.isDoubleFaced()) {
return false;

View File

@@ -479,6 +479,13 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Remembered, sb.toString());
}
public String getSector() {
return get(TrackableProperty.Sector);
}
void updateSector(Card c) {
set(TrackableProperty.Sector, c.getSector());
}
public String getNamedCard() {
return get(TrackableProperty.NamedCard);
}

View File

@@ -157,6 +157,7 @@ public enum Keyword {
SCAVENGE("Scavenge", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."),
SOULBOND("Soulbond", SimpleKeyword.class, true, "You may pair this creature with another unpaired creature when either enters the battlefield. They remain paired for as long as you control both of them."),
SOULSHIFT("Soulshift", KeywordWithAmount.class, false, "When this creature dies, you may return target Spirit card with mana value %d or less from your graveyard to your hand."),
SPACE_SCULPTOR("Space sculptor", SimpleKeyword.class, true, "CARDNAME divides the battlefield into alpha, beta, and gamma sectors. If a creature isn't assigned to a sector, its controller assigns it to one. Opponents assign first."),
SPECIALIZE("Specialize", KeywordWithCost.class, false, "%s, Choose a color, discard a card of that color or associated basic land type: This card perpetually specializes into that color. Activate only as a sorcery."),
SPECTACLE("Spectacle", KeywordWithCost.class, false, "You may cast this spell for its spectacle cost rather than its mana cost if an opponent lost life this turn."),
SPLICE("Splice", KeywordWithCostAndType.class, false, "As you cast an %2$s spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell."),

View File

@@ -1,5 +1,6 @@
package forge.game.player;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -183,6 +184,12 @@ public abstract class PlayerController {
return chooseSomeType(kindOfType, sa, validTypes, invalidTypes, false);
}
public abstract String chooseSector(Card assignee, String ai, List<String> sectors);
public final String chooseSector(Card assignee, String ai) {
final List<String> sectors = Arrays.asList("Alpha", "Beta", "Gamma");
return chooseSector(assignee, ai, sectors);
}
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer);
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);

View File

@@ -68,6 +68,8 @@ public enum TrackableProperty {
ChosenDirection(TrackableTypes.EnumType(Direction.class)),
ChosenEvenOdd(TrackableTypes.EnumType(EvenOdd.class)),
ChosenMode(TrackableTypes.StringType),
ChosenSector(TrackableTypes.StringType),
Sector(TrackableTypes.StringType),
ClassLevel(TrackableTypes.IntegerType),
CurrentRoom(TrackableTypes.StringType),
Intensity(TrackableTypes.IntegerType),

View File

@@ -55,10 +55,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Default harmless implementation for tests.
@@ -496,6 +493,11 @@ public class PlayerControllerForTests extends PlayerController {
return chooseItem(validTypes);
}
@Override
public String chooseSector(Card assignee, String ai, List<String> sectors) {
return chooseItem(sectors);
}
@Override
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
return chooseItem(options);

View File

@@ -1,6 +1,6 @@
Name:Alluring Scent
ManaCost:1 G G
Types:Sorcery
A:SP$ Pump | Cost$ 1 G G | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN All creatures able to block CARDNAME do so. | SpellDescription$ All creatures able to block target creature this turn do so.
A:SP$ Pump | ValidTgts$ Creature | KW$ HIDDEN All creatures able to block CARDNAME do so. | StackDescription$ All creatures able to block {c:Targeted} this turn do so. | SpellDescription$ All creatures able to block target creature this turn do so.
AI:RemoveDeck:All
Oracle:All creatures able to block target creature this turn do so.

View File

@@ -0,0 +1,14 @@
Name:Space Beleren
ManaCost:2 W U
Types:Legendary Planeswalker Jace
Loyalty:3
K:Space sculptor
A:AB$ Effect | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | StaticAbilities$ SectorBlock | SpellDescription$ Creatures in each sector can be blocked this turn only by creatures in the same sector.
SVar:SectorBlock:Mode$ CantBlockBy | ValidAttacker$ Creature | ValidBlockerRelative$ Creature.DifferentSector | Description$ Creatures in each sector can be blocked this turn only by creatures in the same sector.
A:AB$ ChooseSector | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | SubAbility$ DBPutCounterAll | AILogic$ Pump | SpellDescription$ Put a +1/+1 counter on each creature in the sector of your choice.
SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.ChosenSector | CounterType$ P1P1 | StackDescription$ None
A:AB$ ChooseSector | Cost$ SubCounter<5/LOYALTY> | Planeswalker$ True | Ultimate$ True | SubAbility$ DBDestroyAll | AILogic$ Destroy | SpellDescription$ Destroy all creatures in the sector of your choice.
SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature.ChosenSector | StackDescription$ None
DeckHas:Ability$Counters
AI:RemoveDeck:All
Oracle:Space sculptor (Space Beleren divides the battlefield into alpha, beta, and gamma sectors. If a creature isn't assigned to a sector, its controller assigns it to one. Opponents assign first.)\n[+1]: Creatures in each sector can be blocked this turn only by creatures in the same sector.\n[1]: Put a +1/+1 counter on each creature in the sector of your choice.\n[5]: Destroy all creatures in the sector of your choice.

View File

@@ -1231,6 +1231,8 @@ lblSelectANumber=Wähle eine Zahl
lblCut=Ausschneiden
lblCopy=Kopieren
lblPaste=Einfügen
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=Suche
#InputBase.java
@@ -1342,6 +1344,8 @@ lblThereNoCardInPlayerZone=Es sind kein Karten in {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=Lege {0} oben auf Bibliothek oder Friedhof?
lblLibrary=Bibliothek
lblGraveyard=Friedhof
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=Oben drauf
lblBottom=Unten drunter
lblNColorManaFromCard={0} {1} Mana von {2}

View File

@@ -1232,6 +1232,8 @@ lblSelectANumber=Select a number
lblCut=Cut
lblCopy=Copy
lblPaste=Paste
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=Search
#InputBase.java
@@ -1344,6 +1346,8 @@ lblThereNoCardInPlayerZone=There are no cards in {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=Put {0} on the top of library or graveyard?
lblLibrary=Library
lblGraveyard=Graveyard
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=Top
lblBottom=Bottom
lblNColorManaFromCard={0} {1} mana from {2}

View File

@@ -1231,6 +1231,8 @@ lblSelectANumber=Selecciona un número
lblCut=Cortar
lblCopy=Copiar
lblPaste=Pegar
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=Buscar
#InputBase.java
@@ -1342,6 +1344,8 @@ lblThereNoCardInPlayerZone=No hay cartas en {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=¿Poner {0} en la parte superior de la biblioteca o en el cementerio?
lblLibrary=Biblioteca
lblGraveyard=Cementerio
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=Superior
lblBottom=Fondo
lblNColorManaFromCard={0} {1} maná de {2}

View File

@@ -1233,6 +1233,8 @@ lblSelectANumber=Sélectionner un numéro
lblCut=Couper
lblCopy=Copier
lblPaste=Coller
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=Rechercher
#InputBase.java

View File

@@ -1230,6 +1230,8 @@ lblSelectANumber=Seleziona un numero
lblCut=Taglia
lblCopy=Copia
lblPaste=Incolla
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=Ricerca
#InputBase.java
@@ -1342,6 +1344,8 @@ lblThereNoCardInPlayerZone=Non ci sono carte in {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=Metti {0} in cima al grimorio o nel cimitero?
lblLibrary=Grimorio
lblGraveyard=Cimitero
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=Cima
lblBottom=Fondo
lblNColorManaFromCard={0} {1} mana da {2}

View File

@@ -1232,6 +1232,8 @@ lblSelectANumber=数字を選択してください
lblCut=切り取り
lblCopy=コピー
lblPaste=貼り付け
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=検索
#InputBase.java
@@ -1343,6 +1345,8 @@ lblThereNoCardInPlayerZone={0} {1}にカードがありません
lblPutCardsOnTheTopLibraryOrGraveyard=ライブラリまたは墓地の一番上に{0}を置きますか?
lblLibrary=ライブラリー
lblGraveyard=墓地
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=タップ
lblBottom=ボトム
lblNColorManaFromCard={2}から {0} {1} のマナ

View File

@@ -1260,6 +1260,8 @@ lblSelectANumber=Selecione um número
lblCut=Cortar
lblCopy=Copiar
lblPaste=Colar
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=Buscar
#InputBase.java
@@ -1373,6 +1375,8 @@ lblThereNoCardInPlayerZone=Sem cartas em {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=Colocar {0} no topo do grimório ou cemitério?
lblLibrary=Grimório
lblGraveyard=Cemitério
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=Topo
lblBottom=Fundo
lblNColorManaFromCard={0} {1} mana de {2}

View File

@@ -1232,6 +1232,8 @@ lblSelectANumber=选择一个数
lblCut=剪切
lblCopy=复制
lblPaste=粘贴
#GameAction.java
lblAssigns=assigns:
#ListChooser.java
lblSearch=搜索
#InputBase.java
@@ -1344,6 +1346,8 @@ lblThereNoCardInPlayerZone={0}的{1}中没有牌
lblPutCardsOnTheTopLibraryOrGraveyard=将{0}放到牌库顶还是坟场?
lblLibrary=牌库
lblGraveyard=坟场
lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector
lblTop=
lblBottom=
lblNColorManaFromCard={2}产{0}个{1}法术力

View File

@@ -527,6 +527,14 @@ public class CardDetailUtil {
area.append("(Class Level:").append(card.getClassLevel()).append(")");
}
// sector
if (!card.getSector().isEmpty()) {
if (area.length() != 0) {
area.append("\n");
}
area.append("Sector: ").append(card.getSector());
}
// a card has something attached to it
if (card.hasCardAttachments()) {
if (area.length() != 0) {

View File

@@ -1364,6 +1364,18 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
}
@Override
public String chooseSector(Card assignee, String ai, List<String> sectors) {
String prompt;
if (assignee != null) {
String creature = CardTranslation.getTranslatedName(assignee.getName()) + " (" + assignee.getId() + ")";
prompt = Localizer.getInstance().getMessage("lblAssignSectorCreature", creature);
} else {
prompt = Localizer.getInstance().getMessage("lblChooseSectorEffect");
}
return getGui().one(prompt, sectors);
}
@Override
public Object vote(final SpellAbility sa, final String prompt, final List<Object> options,
final ListMultimap<Object, Player> votes, Player forPlayer) {