space_beleren.txt and support

This commit is contained in:
Northmoc
2022-11-15 09:32:01 -05:00
parent 8eca58e9e4
commit 15b7909171
15 changed files with 139 additions and 10 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,12 @@ public class PlayerControllerAi extends PlayerController {
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
}
@Override
public String chooseSector(Card assignee, String ai) {
final List<String> sectors = Arrays.asList("Alpha", "Beta", "Gamma");
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,26 @@ 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);
checkAgain = true;
}
}
for (Card assignee : toAssign) { // probably would be nice for players to pick order of assigning?
assignee.assignSector(p.getController().chooseSector(assignee, "Assign"));
toAssign.remove(assignee);
}
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

View File

@@ -45,6 +45,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,16 @@
package forge.game.ability.effects;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.player.Player;
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

@@ -183,6 +183,8 @@ public abstract class PlayerController {
return chooseSomeType(kindOfType, sa, validTypes, invalidTypes, false);
}
public abstract String chooseSector(Card assignee, String ai);
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,12 @@ public class PlayerControllerForTests extends PlayerController {
return chooseItem(validTypes);
}
@Override
public String chooseSector(Card assignee, String ai) {
final List<String> sectors = Arrays.asList("Alpha", "Beta", "Gamma");
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

@@ -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

@@ -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,17 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
}
}
@Override
public String chooseSector(Card assignee, String ai) {
final List<String> sectors = Arrays.asList("Alpha", "Beta", "Gamma");
// turn this into two separate localized prompts
String prompt = "Choose sector";
if (assignee != null) {
prompt = prompt + " for " + assignee.getName();
}
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) {