Add localization strings, draw markers, and updated Oubliette room

This commit is contained in:
Lyu Zong-Hong
2021-06-27 15:22:33 +09:00
parent 59e82a33ab
commit ed0c5aac5e
13 changed files with 188 additions and 38 deletions

View File

@@ -50,7 +50,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
game.resetPlayersAttackedOnNextTurn();
game.resetPlayersAttackedOnNextTurn();
GameAction action = game.getAction();
for (Player p: players) {
p.setStartingLife(p.getStartingLife());
p.clearCounters();
@@ -58,6 +58,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
p.onCleanupPhase();
p.setLandsPlayedLastTurn(0);
p.resetCommanderStats();
p.resetCompletedDungeons();
CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false));
List<Card> filteredCards = null;
@@ -74,7 +75,7 @@ public class RestartGameEffect extends SpellAbilityEffect {
}
}
p.getZone(ZoneType.Command).removeAllCards(true);
for (Card c : newLibrary) {
action.moveToLibrary(c, 0, sa);
}
@@ -85,15 +86,15 @@ public class RestartGameEffect extends SpellAbilityEffect {
trigHandler.clearSuppression(TriggerType.Shuffled);
trigHandler.clearSuppression(TriggerType.ChangesZone);
game.resetTurnOrder();
game.setAge(GameStage.RestartedByKarn);
// Do not need this because ability will resolve only during that player's turn
//game.getPhaseHandler().setPlayerTurn(sa.getActivatingPlayer());
// Set turn number?
// The rest is handled by phaseHandler
// The rest is handled by phaseHandler
}
/* (non-Javadoc)

View File

@@ -1,5 +1,6 @@
package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -62,18 +63,18 @@ public class SacrificeEffect extends SpellAbilityEffect {
Cost cumCost = new Cost(sa.getParam("CumulativeUpkeep"), true);
Cost payCost = new Cost(ManaCost.ZERO, true);
int n = card.getCounters(CounterEnumType.AGE);
// multiply cost
for (int i = 0; i < n; ++i) {
payCost.add(cumCost);
}
sa.setCumulativeupkeep(true);
game.updateLastStateForCard(card);
StringBuilder sb = new StringBuilder();
sb.append("Cumulative upkeep for ").append(card);
boolean isPaid = activator.getController().payManaOptional(card, payCost, sa, sb.toString(), ManaPaymentPurpose.CumulativeUpkeep);
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(card);
runParams.put(AbilityKey.CumulativeUpkeepPaid, isPaid);
@@ -90,6 +91,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
final List<Player> tgts = getTargetPlayers(sa);
final boolean devour = sa.hasParam("Devour");
final boolean exploit = sa.hasParam("Exploit");
final boolean sacEachValid = sa.hasParam("SacEachValid");
String valid = sa.getParam("SacValid");
if (valid == null) {
@@ -118,32 +120,63 @@ public class SacrificeEffect extends SpellAbilityEffect {
}
}
}
}
else {
} else {
CardCollectionView choosenToSacrifice = null;
for (final Player p : tgts) {
CardCollectionView battlefield = p.getCardsIn(ZoneType.Battlefield);
CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, valid, sa);
if (!destroy) {
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa));
}
if (sa.hasParam("Random")) {
choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection());
} else if (sa.hasParam("OptionalSacrifice") && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) {
choosenToSacrifice = CardCollection.EMPTY;
if (sacEachValid) { // Sacrifice maximum permanents in any combination of types specified by SacValid
String [] validArray = valid.split(" & ");
String [] msgArray = msg.split(" & ");
List<CardCollection> validTargetsList = new ArrayList<>(validArray.length);
for (String subValid : validArray) {
CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, subValid, sa);
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa));
validTargetsList.add(new CardCollection(validTargets));
}
CardCollection chosenCards = new CardCollection();
for (int i = 0; i < validArray.length; ++i) {
CardCollection validTargets = validTargetsList.get(i);
if (validTargets.isEmpty()) continue;
if (validTargets.size() > 1 && i < validArray.length - 1) {
// remove candidates that must be chosen for later types
CardCollection union = new CardCollection();
for (int j = i + 1; j < validArray.length; ++j) {
union.addAll(validTargetsList.get(j));
if (union.size() == (j - i) * amount) {
validTargets.removeAll(union);
}
}
}
choosenToSacrifice = p.getController().choosePermanentsToSacrifice(sa, amount, amount, validTargets, msgArray[i]);
for (int j = i + 1; j < validArray.length; ++j) {
validTargetsList.get(j).removeAll(choosenToSacrifice);
}
chosenCards.addAll(choosenToSacrifice);
}
choosenToSacrifice = chosenCards;
} else {
boolean isOptional = sa.hasParam("Optional");
boolean isStrict = sa.hasParam("StrictAmount");
int minTargets = isOptional ? 0 : amount;
boolean notEnoughTargets = isStrict && validTargets.size() < minTargets;
if (!notEnoughTargets) {
choosenToSacrifice = destroy ?
p.getController().choosePermanentsToDestroy(sa, minTargets, amount, validTargets, msg) :
p.getController().choosePermanentsToSacrifice(sa, minTargets, amount, validTargets, msg);
} else {
CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, valid, sa);
if (!destroy) {
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa));
}
if (sa.hasParam("Random")) {
choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size()), new CardCollection());
} else if (sa.hasParam("OptionalSacrifice") && !p.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantSacrifice"))) {
choosenToSacrifice = CardCollection.EMPTY;
} else {
boolean isOptional = sa.hasParam("Optional");
boolean isStrict = sa.hasParam("StrictAmount");
int minTargets = isOptional ? 0 : amount;
boolean notEnoughTargets = isStrict && validTargets.size() < minTargets;
if (!notEnoughTargets) {
choosenToSacrifice = destroy ?
p.getController().choosePermanentsToDestroy(sa, minTargets, amount, validTargets, msg) :
p.getController().choosePermanentsToSacrifice(sa, minTargets, amount, validTargets, msg);
} else {
choosenToSacrifice = CardCollection.EMPTY;
}
}
}

View File

@@ -49,7 +49,7 @@ public class VentureEffect extends SpellAbilityEffect {
for (PaperCard pc : dungeonCards) {
faces.add(pc.getRules().getMainPart());
}
String message = Localizer.getInstance().getMessage("lblChooseACardName");
String message = Localizer.getInstance().getMessage("lblChooseDungeon");
String chosen = player.getController().chooseCardName(sa, faces, message);
Card dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(chosen), player);
@@ -81,7 +81,7 @@ public class VentureEffect extends SpellAbilityEffect {
}
}
}
final String title = Localizer.getInstance().getMessage("lblChooseAbilityForObject", dungeon.toString());
final String title = Localizer.getInstance().getMessage("lblChooseRoom");
SpellAbility chosen = player.getController().chooseSingleSpellForEffect(candidates, sa, title, null);
return chosen.getParam("RoomName");
} else {

View File

@@ -1954,6 +1954,9 @@ public class Player extends GameEntity implements Comparable<Player> {
public void addCompletedDungeon(Card dungeon) {
completedDungeons.add(dungeon);
}
public void resetCompletedDungeons() {
completedDungeons.clear();
}
public final void altWinBySpellEffect(final String sourceName) {
if (cantWin()) {

View File

@@ -510,6 +510,13 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
}
if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) {
List<String> markers = new ArrayList<>();
markers.add("In Room:");
markers.add(card.getCurrentRoom());
drawMarkersTabs(g, markers);
}
final int combatXSymbols = (cardXOffset + (cardWidth / 4)) - 16;
final int stateXSymbols = (cardXOffset + (cardWidth / 2)) - 16;
final int ySymbols = (cardYOffset + cardHeight) - (cardHeight / 8) - 16;
@@ -840,6 +847,57 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
}
private void drawMarkersTabs(final Graphics g, List<String> markers) {
final Dimension imgSize = calculateImageSize();
final int titleY = Math.round(imgSize.height * (54f / 640)) - 15;
final int spaceFromTopOfCard = titleY + 60;
final int markerBoxHeight = 24;
final int markerBoxBaseWidth = 14;
final int markerBoxSpacing = 2;
int currentMarker = 0;
FontMetrics smallFontMetrics = g.getFontMetrics(smallCounterFont);
for (String marker : markers) {
final int markerBoxRealWidth = markerBoxBaseWidth + smallFontMetrics.stringWidth(marker);
final int markerYOffset;
if (ForgeConstants.CounterDisplayLocation.from(FModel.getPreferences().getPref(FPref.UI_CARD_COUNTER_DISPLAY_LOCATION)) == ForgeConstants.CounterDisplayLocation.TOP) {
markerYOffset = cardYOffset + spaceFromTopOfCard - markerBoxHeight + currentMarker++ * (markerBoxHeight + markerBoxSpacing);
} else {
markerYOffset = cardYOffset + cardHeight - spaceFromTopOfCard / 2 - markerBoxHeight + currentMarker++ * (markerBoxHeight + markerBoxSpacing);
}
if (isSelected) {
g.setColor(new Color(0, 0, 0, 255));
} else {
g.setColor(new Color(0, 0, 0, 200));
}
RoundRectangle2D markerArea = new RoundRectangle2D.Float(cardXOffset, markerYOffset, markerBoxRealWidth, markerBoxHeight, 9, 9);
((Graphics2D) g).fill(markerArea);
g.fillRect(cardXOffset, markerYOffset, 9, markerBoxHeight);
if (isSelected) {
g.setColor(new Color(200, 200, 200));
} else {
g.setColor(new Color(200, 200, 200, 180));
}
Rectangle nameBounds = markerArea.getBounds();
nameBounds.x += 8;
nameBounds.y -= 1;
nameBounds.width = 43;
drawVerticallyCenteredString(g, marker, nameBounds, smallCounterFont, smallFontMetrics);
}
}
/**
* Draws a String justified to the left of the rectangle, centered vertically.
*

View File

@@ -113,7 +113,7 @@ public class CardRenderer {
static {
try {
for (int fontSize = 11; fontSize <= 22; fontSize++) {
for (int fontSize = 8; fontSize <= 22; fontSize++) {
generateFontForCounters(fontSize);
}
} catch (Exception e) {
@@ -603,6 +603,13 @@ public class CardRenderer {
}
if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) {
List<String> markers = new ArrayList<>();
markers.add("In Room:");
markers.add(card.getCurrentRoom());
drawMarkersTabs(markers, g, x, y, w, h);
}
float otherSymbolsSize = w / 4f;
final float combatXSymbols = (x + (w / 4)) - otherSymbolsSize / 2 - 10;
final float stateXSymbols = (x + (w / 2)) - otherSymbolsSize / 2 - 10;
@@ -1073,6 +1080,38 @@ public class CardRenderer {
}
private static void drawMarkersTabs(final List<String> markers, final Graphics g, final float x, final float y, final float w, final float h) {
int fontSize = Math.max(8, Math.min(22, (int) (h * 0.05)));
BitmapFont font = counterFonts.get(fontSize);
final float additionalXOffset = 3f * ((fontSize - 8) / 8f);
float otherSymbolsSize = w / 3.5f;
final float ySymbols = (h / 12) - otherSymbolsSize / 2;
final float markerBoxHeight = 9 + fontSize;
final float markerBoxBaseWidth = 8 + additionalXOffset * 2;
final float markerBoxSpacing = -4;
final float spaceFromTopOfCard = y + h - markerBoxHeight - markerBoxSpacing - otherSymbolsSize + ySymbols;
int markerCounter = markers.size() - 1;
for (String marker : markers) {
layout.setText(font, marker);
final float markerBoxRealWidth = markerBoxBaseWidth + layout.width + 4;
final float markerYOffset = spaceFromTopOfCard - (markerCounter-- * (markerBoxHeight + markerBoxSpacing));
g.fillRect(counterBackgroundColor, x - 3, markerYOffset, markerBoxRealWidth, markerBoxHeight);
Color markerColor = new Color(200.0f / 255.0f, 200.0f / 255.0f, 200.0f / 255.0f, 1.0f);
drawText(g, marker, font, markerColor, x + 2 + additionalXOffset, markerYOffset, markerBoxRealWidth, markerBoxHeight, Align.left);
}
}
private static void drawPtBox(Graphics g, CardView card, CardStateView details, Color color, float x, float y, float w, float h) {
//use array of strings to render separately with a tiny amount of space in between
//instead of using actual spaces which are too wide
@@ -1202,7 +1241,7 @@ public class CardRenderer {
int pageSize = 128;
//only generate images for characters that could be used by Forge
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890/-+";
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890/-+:'";
final PixmapPacker packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 2, false);
final FreeTypeFontParameter parameter = new FreeTypeFontParameter();

View File

@@ -6,9 +6,7 @@ SVar:DBEntry:DB$ LoseLife | Defined$ Player | LifeAmount$ 1 | RoomName$ Trapped
SVar:DBVeilsOfFear:DB$ RepeatEach | RepeatSubAbility$ DBLoseLife1 | RepeatPlayers$ Player | RoomName$ Veils of Fear | SpellDescription$ Each player loses 2 life unless they discard a card. | NextRoom$ DBSandfallCell
SVar:DBLoseLife1:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered
SVar:DBOubliette:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBSacArtifact | RoomName$ Oubliette | SpellDescription$ Discard a card and sacrifice an artifact, a creature, and a land. | NextRoom$ DBCradleDeathGod
SVar:DBSacArtifact:DB$ Sacrifice | Defined$ You | SacValid$ Artifact | SubAbility$ DBSacCreature
SVar:DBSacCreature:DB$ Sacrifice | Defined$ You | SacValid$ Creature | SubAbility$ DBSacLand
SVar:DBSacLand:DB$ Sacrifice | Defined$ You | SacValid$ Land
SVar:DBSacArtifact:DB$ Sacrifice | Defined$ You | SacValid$ Artifact & Creature & Land | SacEachValid$ True
SVar:DBSandfallCell:DB$ RepeatEach | RepeatSubAbility$ DBLoseLife2 | RepeatPlayers$ Player | RoomName$ Sandfall Cell | SpellDescription$ Each player loses 2 life unless they sacrifice an artifact, a creature, or a land. | NextRoom$ DBCradleDeathGod
SVar:DBLoseLife2:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Sac<1/Artifact;Creature;Land/an artifact, a creature, or a land> | UnlessPayer$ Remembered
SVar:DBCradleDeathGod:DB$ Token | TokenScript$ b_4_4_the_atropal_deathtouch | TokenOwner$ You | RoomName$ Cradle of the Death God | SpellDescription$ Create The Atropal, a legendary 4/4 black God Horror creature token with deathtouch.

View File

@@ -1960,6 +1960,9 @@ lblChoosesPile=Wähle Stapel
lblEmptyPile=Leerer Stapel
#UntapEffect.java
lblSelectCardToUntap=Wähle Karten zum Enttappen
#VentureEffect.java
lblChooseDungeon=Which dungeon do you want to venture into?
lblChooseRoom=Which room do you want to venture into?
#VoteEffect.java
lblVote=Abstimmung
lblCurrentVote=Aktuelle Stimmen

View File

@@ -1960,6 +1960,9 @@ lblChoosesPile=chooses Pile
lblEmptyPile=Empty pile
#UntapEffect.java
lblSelectCardToUntap=Select cards to untap
#VentureEffect.java
lblChooseDungeon=Which dungeon do you want to venture into?
lblChooseRoom=Which room do you want to venture into?
#VoteEffect.java
lblVote=Vote
lblCurrentVote=Current Votes

View File

@@ -1960,6 +1960,9 @@ lblChoosesPile=elige Montón
lblEmptyPile=Montón vacío
#UntapEffect.java
lblSelectCardToUntap=Selecciona las cartas que quieres enderezar
#VentureEffect.java
lblChooseDungeon=Which dungeon do you want to venture into?
lblChooseRoom=Which room do you want to venture into?
#VoteEffect.java
lblVote=Votar
lblCurrentVote=Votos actuales

View File

@@ -1960,6 +1960,9 @@ lblChoosesPile=sceglie la Pila
lblEmptyPile=Pila vuota
#UntapEffect.java
lblSelectCardToUntap=Scegli le carte da Stappare
#VentureEffect.java
lblChooseDungeon=Which dungeon do you want to venture into?
lblChooseRoom=Which room do you want to venture into?
#VoteEffect.java
lblVote=Voto
lblCurrentVote=Voti attuali

View File

@@ -1961,6 +1961,9 @@ lblChoosesPile=束を選んだ
lblEmptyPile=空っぽの束
#UntapEffect.java
lblSelectCardToUntap=アンタップするカードを選ぶ
#VentureEffect.java
lblChooseDungeon=どのダンジョンを探索しますか?
lblChooseRoom=どの部屋を探索しますか?
#VoteEffect.java
lblVote=投票
lblCurrentVote=現在の投票

View File

@@ -1961,6 +1961,9 @@ lblChoosesPile=选择堆
lblEmptyPile=空堆
#UntapEffect.java
lblSelectCardToUntap=选择要重置的牌
#VentureEffect.java
lblChooseDungeon=Which dungeon do you want to venture into?
lblChooseRoom=Which room do you want to venture into?
#VoteEffect.java
lblVote=投票
lblCurrentVote=当前投票