mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
Add Speed Tracker to Command Zone (#6982)
* Add command zone effect displaying speed * Remove enum counter type for speed. * Make Start Your Engines an SBA. * LifeLost -> LifeLostAll per speed rules. * Use same game event for all speed changes. * Fix keyword not appearing in detail text * Cleanup extra createSpeedEffect. * Add support for arbitrary overlay text. Remove fake counters. * Text styling. * Remove extra SBA check. * Remove speed from PlayerView; localization support. --------- Co-authored-by: Jetz <Jetz722@gmail.com>
This commit is contained in:
@@ -340,6 +340,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
protected boolean renderForUi = true;
|
||||
private final CardView view;
|
||||
|
||||
private String overlayText = null;
|
||||
|
||||
private SpellAbility[] basicLandAbilities = new SpellAbility[MagicColor.WUBRG.length];
|
||||
|
||||
private int planeswalkerAbilityActivated;
|
||||
@@ -6826,6 +6828,17 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return getType().hasStringType("Class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a string as an overlay on top of this card (similar to the way counter text is shown).
|
||||
*/
|
||||
public void setOverlayText(String overlayText) {
|
||||
this.overlayText = overlayText;
|
||||
view.updateMarkerText(this);
|
||||
}
|
||||
public String getOverlayText() {
|
||||
return this.overlayText;
|
||||
}
|
||||
|
||||
public final void animateBestow() {
|
||||
animateBestow(true);
|
||||
}
|
||||
|
||||
@@ -491,6 +491,7 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
void updateCurrentRoom(Card c) {
|
||||
set(TrackableProperty.CurrentRoom, c.getCurrentRoom());
|
||||
updateMarkerText(c);
|
||||
}
|
||||
|
||||
public int getIntensity() {
|
||||
@@ -514,6 +515,7 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
void updateClassLevel(Card c) {
|
||||
set(TrackableProperty.ClassLevel, c.getClassLevel());
|
||||
updateMarkerText(c);
|
||||
}
|
||||
|
||||
public int getRingLevel() {
|
||||
@@ -525,6 +527,41 @@ public class CardView extends GameEntityView {
|
||||
set(TrackableProperty.RingLevel, p.getNumRingTemptedYou());
|
||||
}
|
||||
|
||||
public String getOverlayText() {
|
||||
return get(TrackableProperty.OverlayText);
|
||||
}
|
||||
public List<String> getMarkerText() {
|
||||
return get(TrackableProperty.MarkerText);
|
||||
}
|
||||
void updateMarkerText(Card c) {
|
||||
List<String> markerItems = new ArrayList<>();
|
||||
if(c.getCurrentRoom() != null && !c.getCurrentRoom().isEmpty()) {
|
||||
markerItems.add("In Room:");
|
||||
markerItems.add(c.getCurrentRoom());
|
||||
}
|
||||
if(c.isClassCard() && c.isInZone(ZoneType.Battlefield)) {
|
||||
markerItems.add("CL:" + c.getClassLevel());
|
||||
}
|
||||
if(getRingLevel() > 0) {
|
||||
markerItems.add("RL:" + getRingLevel());
|
||||
}
|
||||
|
||||
if(StringUtils.isNotEmpty(c.getOverlayText())) {
|
||||
set(TrackableProperty.OverlayText, c.getOverlayText());
|
||||
markerItems.add(c.getOverlayText());
|
||||
}
|
||||
else {
|
||||
//Overlay text is any custom string. It gets mixed in with the other marker lines, but it also needs its
|
||||
//own property so that it can display in the card detail text.
|
||||
set(TrackableProperty.OverlayText, null);
|
||||
}
|
||||
|
||||
if(markerItems.isEmpty())
|
||||
set(TrackableProperty.MarkerText, null);
|
||||
else
|
||||
set(TrackableProperty.MarkerText, markerItems);
|
||||
}
|
||||
|
||||
private String getRemembered() {
|
||||
return get(TrackableProperty.Remembered);
|
||||
}
|
||||
@@ -974,6 +1011,7 @@ public class CardView extends GameEntityView {
|
||||
updateDamage(c);
|
||||
updateSpecialize(c);
|
||||
updateRingLevel(c);
|
||||
updateMarkerText(c);
|
||||
|
||||
if (c.getIntensity(false) > 0) {
|
||||
updateIntensity(c);
|
||||
|
||||
@@ -95,8 +95,6 @@ public enum CounterEnumType {
|
||||
|
||||
CORRUPTION("CRPTN", 210, 121, 210),
|
||||
|
||||
CRANK("CRANK!", 181, 148, 15),
|
||||
|
||||
CROAK("CROAK", 155, 255, 5),
|
||||
|
||||
CREDIT("CRDIT", 188, 197, 234),
|
||||
|
||||
@@ -189,7 +189,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private final Map<Card, Integer> commanderCast = Maps.newHashMap();
|
||||
private final Map<Card, Integer> commanderDamage = Maps.newHashMap();
|
||||
private DetachedCardEffect commanderEffect = null;
|
||||
private DetachedCardEffect speedEffect;
|
||||
|
||||
private Card monarchEffect;
|
||||
private Card initiativeEffect;
|
||||
@@ -197,6 +196,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private Card contraptionSprocketEffect;
|
||||
private Card radiationEffect;
|
||||
private Card keywordEffect;
|
||||
private Card speedEffect;
|
||||
|
||||
private Map<Long, Integer> additionalVotes = Maps.newHashMap();
|
||||
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
|
||||
@@ -1971,20 +1971,19 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
return speed;
|
||||
}
|
||||
public final void increaseSpeed() {
|
||||
if (speedEffect == null) createSpeedEffect();
|
||||
if (!maxSpeed()) { // can't increase past 4
|
||||
int old = speed;
|
||||
speed++;
|
||||
view.updateSpeed(this);
|
||||
getGame().fireEvent(new GameEventSpeedChanged(this, old, speed)); //play sound effect
|
||||
updateSpeedEffect();
|
||||
}
|
||||
}
|
||||
public final void decreaseSpeed() {
|
||||
if (speed > 1) { // can't decrease speed below 1
|
||||
int old = speed;
|
||||
speed--;
|
||||
view.updateSpeed(this);
|
||||
game.fireEvent(new GameEventSpeedChanged(this, old, speed));
|
||||
updateSpeedEffect();
|
||||
}
|
||||
}
|
||||
public final boolean noSpeed() {
|
||||
@@ -1995,22 +1994,62 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
public final void setSpeed(int i) { //just used for copy/save
|
||||
speed = i;
|
||||
if (speed > 0) view.updateSpeed(this);
|
||||
if(this.speedEffect != null)
|
||||
updateSpeedEffect();
|
||||
}
|
||||
public final void createSpeedEffect() {
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
DetachedCardEffect eff = new DetachedCardEffect(this, "Speed Effect");
|
||||
if(this.speedEffect != null || this.noSpeed())
|
||||
return;
|
||||
|
||||
speedEffect = new Card(game.nextCardId(), null, game);
|
||||
speedEffect.setOwner(this);
|
||||
speedEffect.setGamePieceType(GamePieceType.EFFECT);
|
||||
|
||||
speedEffect.addAlternateState(CardStateName.Flipped, false);
|
||||
CardState speedFront = speedEffect.getState(CardStateName.Original);
|
||||
CardState speedBack = speedEffect.getState(CardStateName.Flipped);
|
||||
|
||||
speedFront.setImageKey("t:speed");
|
||||
speedFront.setName("Start Your Engines!");
|
||||
|
||||
speedBack.setImageKey("t:max_speed");
|
||||
speedBack.setName("Max Speed!");
|
||||
|
||||
String label = Localizer.getInstance().getMessage("lblSpeed", this.speed);
|
||||
speedEffect.setOverlayText(label);
|
||||
|
||||
// 702.179d There is an inherent triggered ability associated with a player having 1 or more speed. This ability has no source and is controlled by that player.
|
||||
// That ability is “Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.”
|
||||
String trigger = "Mode$ LifeLostAll | ValidPlayer$ Opponent | TriggerZones$ Command | ActivationLimit$ 1 | " +
|
||||
"PlayerTurn$ True | CheckSVar$ Count$YourSpeed | SVarCompare$ LT4 | "
|
||||
+ "TriggerDescription$ Whenever one or more opponents lose life during your turn, if your speed is less than 4, your speed increases by 1. This ability triggers only once each turn.";
|
||||
String speedUp = "DB$ ChangeSpeed";
|
||||
Trigger lifeLostTrigger = TriggerHandler.parseTrigger(trigger, eff, true);
|
||||
lifeLostTrigger.setOverridingAbility(AbilityFactory.getAbility(speedUp, eff));
|
||||
eff.addTrigger(lifeLostTrigger);
|
||||
this.speedEffect = eff;
|
||||
com.add(eff);
|
||||
Trigger lifeLostTrigger = TriggerHandler.parseTrigger(trigger, speedEffect, true);
|
||||
lifeLostTrigger.setOverridingAbility(AbilityFactory.getAbility(speedUp, speedEffect));
|
||||
speedFront.addTrigger(lifeLostTrigger);
|
||||
|
||||
speedEffect.updateStateForView();
|
||||
|
||||
if(this.maxSpeed())
|
||||
speedEffect.setState(CardStateName.Flipped, true);
|
||||
|
||||
final PlayerZone com = getZone(ZoneType.Command);
|
||||
com.add(speedEffect);
|
||||
this.updateZoneForView(com);
|
||||
}
|
||||
protected void updateSpeedEffect() {
|
||||
if(this.speedEffect == null) {
|
||||
if(this.noSpeed())
|
||||
return;
|
||||
createSpeedEffect();
|
||||
}
|
||||
Localizer localizer = Localizer.getInstance();
|
||||
String label = this.maxSpeed() ? localizer.getMessage("lblMaxSpeed") : localizer.getMessage("lblSpeed", this.speed);
|
||||
speedEffect.setOverlayText(label);
|
||||
if(maxSpeed() && speedEffect.getCurrentStateName() == CardStateName.Original)
|
||||
speedEffect.setState(CardStateName.Flipped, true);
|
||||
else if(!maxSpeed() && speedEffect.getCurrentStateName() == CardStateName.Flipped)
|
||||
speedEffect.setState(CardStateName.Original, true);
|
||||
}
|
||||
|
||||
public final List<Card> getPlaneswalkedToThisTurn() {
|
||||
@@ -3990,8 +4029,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public void setCrankCounter(int counters) {
|
||||
this.crankCounter = counters;
|
||||
if (this.contraptionSprocketEffect != null) {
|
||||
Map<CounterType, Integer> counterMap = Map.of(CounterType.get(CounterEnumType.CRANK), this.crankCounter);
|
||||
contraptionSprocketEffect.setCounters(counterMap);
|
||||
String label = Localizer.getInstance().getMessage("lblCrank", this.crankCounter);
|
||||
contraptionSprocketEffect.setOverlayText(label);
|
||||
}
|
||||
else if (this.getCardsIn(ZoneType.Battlefield).anyMatch(CardPredicates.CONTRAPTIONS)) {
|
||||
this.createContraptionSprockets();
|
||||
@@ -4017,12 +4056,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
contraptionSprocketEffect.setName("Contraption Sprockets");
|
||||
contraptionSprocketEffect.setGamePieceType(GamePieceType.EFFECT);
|
||||
|
||||
//Add "counters" on the effect to represent the current CRANK counter position.
|
||||
//This and some other un-cards could benefit from a distinct system for positional counters or markers,
|
||||
//see for instance Baron von Count or B-I-N-G-O. For now this is sufficient to display the current sprocket
|
||||
//on the counter overlay, and I don't think any existing effect will notice it.
|
||||
Map<CounterType, Integer> counterMap = Map.of(CounterType.get(CounterEnumType.CRANK), this.crankCounter);
|
||||
contraptionSprocketEffect.setCounters(counterMap);
|
||||
String label = Localizer.getInstance().getMessage("lblCrank", this.crankCounter);
|
||||
contraptionSprocketEffect.setOverlayText(label);
|
||||
contraptionSprocketEffect.setText("At the beginning of your upkeep, if you control a Contraption, move the CRANK! counter to the next sprocket and crank any number of that sprocket's Contraptions.");
|
||||
|
||||
contraptionSprocketEffect.updateStateForView();
|
||||
|
||||
@@ -329,13 +329,6 @@ public class PlayerView extends GameEntityView {
|
||||
set(TrackableProperty.ControlVotes, val);
|
||||
}
|
||||
|
||||
public int getSpeed() {
|
||||
return get(TrackableProperty.Speed);
|
||||
}
|
||||
void updateSpeed(Player p) {
|
||||
set(TrackableProperty.Speed, p.getSpeed());
|
||||
}
|
||||
|
||||
public int getAdditionalVillainousChoices() {
|
||||
return get(TrackableProperty.AdditionalVillainousChoices);
|
||||
}
|
||||
@@ -604,10 +597,6 @@ public class PlayerView extends GameEntityView {
|
||||
}
|
||||
details.add(Localizer.getInstance().getMessage("lblExtraTurnCountHas", String.valueOf(getExtraTurnCount())));
|
||||
|
||||
if (getSpeed() > 0) {
|
||||
details.add(Localizer.getInstance().getMessage("lblSpeed", String.valueOf(getSpeed())));
|
||||
}
|
||||
|
||||
final String keywords = Lang.joinHomogenous(getDisplayableKeywords());
|
||||
if (!keywords.isEmpty()) {
|
||||
details.add(keywords);
|
||||
|
||||
@@ -85,6 +85,8 @@ public enum TrackableProperty {
|
||||
RingLevel(TrackableTypes.IntegerType),
|
||||
CurrentRoom(TrackableTypes.StringType),
|
||||
Intensity(TrackableTypes.IntegerType),
|
||||
OverlayText(TrackableTypes.StringType),
|
||||
MarkerText(TrackableTypes.StringListType),
|
||||
Remembered(TrackableTypes.StringType),
|
||||
NamedCard(TrackableTypes.StringListType),
|
||||
PlayerMayLook(TrackableTypes.PlayerViewCollectionType, FreezeMode.IgnoresFreeze),
|
||||
@@ -217,7 +219,6 @@ public enum TrackableProperty {
|
||||
CommanderCast(TrackableTypes.IntegerMapType),
|
||||
CommanderDamage(TrackableTypes.IntegerMapType),
|
||||
MindSlaveMaster(TrackableTypes.PlayerViewType),
|
||||
Speed(TrackableTypes.IntegerType),
|
||||
|
||||
Ante(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze),
|
||||
Battlefield(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze), //zones can't respect freeze, otherwise cards that die from state based effects won't have that reflected in the UI
|
||||
|
||||
@@ -533,11 +533,8 @@ 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);
|
||||
if(card.getMarkerText() != null) {
|
||||
drawMarkersTabs(g, card.getMarkerText());
|
||||
}
|
||||
|
||||
final int combatXSymbols = (cardXOffset + (cardWidth / 4)) - 16;
|
||||
|
||||
@@ -781,23 +781,12 @@ 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, false);
|
||||
}
|
||||
//Class level
|
||||
if (card.getCurrentState().getType().hasStringType("Class") && ZoneType.Battlefield.equals(card.getZone())) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("CL:" + card.getClassLevel());
|
||||
drawMarkersTabs(markers, g, x, y - markersHeight, w, h, true);
|
||||
}
|
||||
//Ring level
|
||||
if (card.getRingLevel() > 0) {
|
||||
List<String> markers = new ArrayList<>();
|
||||
markers.add("RL:" + card.getRingLevel());
|
||||
drawMarkersTabs(markers, g, x, y - markersHeight, w, h, true);
|
||||
if(card.getMarkerText() != null) {
|
||||
List<String> markers = card.getMarkerText();
|
||||
if(markers.size() > 1) //Use smaller text for multi-line strings.
|
||||
drawMarkersTabs(markers, g, x, y, w, h, false);
|
||||
else
|
||||
drawMarkersTabs(markers, g, x, y - markersHeight, w, h, true);
|
||||
}
|
||||
|
||||
float otherSymbolsSize = w / 4f;
|
||||
@@ -1528,7 +1517,7 @@ public class CardRenderer {
|
||||
int pageSize = 128;
|
||||
|
||||
//only generate images for characters that could be used by Forge
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890/-+:'";
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890/-+:'!—";
|
||||
|
||||
final PixmapPacker packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 2, false);
|
||||
final FreeTypeFontParameter parameter = new FreeTypeFontParameter();
|
||||
|
||||
@@ -2347,7 +2347,6 @@ lblAntedHas=Ante''d: {0}
|
||||
lblAdditionalVotes=You get {0} additional votes.
|
||||
lblOptionalAdditionalVotes=You may vote {0} additional times.
|
||||
lblControlsVote=You choose how each player votes.
|
||||
lblSpeed=Speed: {0}
|
||||
#VStack.java
|
||||
lblAlwaysYes=Always Yes
|
||||
lblAlwaysNo=Always No
|
||||
@@ -3007,6 +3006,9 @@ lblChooseACompanion=Choose a companion
|
||||
lblChooseAColorFor=Choose a color for {0}
|
||||
lblRevealFaceDownCards=Revealing face-down cards from
|
||||
lblLearnALesson=Learn a Lesson
|
||||
lblSpeed=SPEED: {0}
|
||||
lblMaxSpeed=SPEED: MAX!
|
||||
lblCrank=CRANK! — {0}
|
||||
#QuestPreferences.java
|
||||
lblWildOpponentNumberError=Wild Opponents can only be 0 to 3
|
||||
#GauntletWinLose.java
|
||||
|
||||
@@ -520,13 +520,19 @@ public class CardDetailUtil {
|
||||
// class level
|
||||
if (card.getId() >= 0 && card.getCurrentState().getType().hasStringType("Class") && card.getZone() == ZoneType.Battlefield) {
|
||||
area.append("\n\n");
|
||||
area.append("(Class Level:").append(card.getClassLevel()).append(")");
|
||||
area.append("(Class Level: ").append(card.getClassLevel()).append(")");
|
||||
}
|
||||
|
||||
//ring level
|
||||
if (card.getRingLevel() > 0 && card.getZone() == ZoneType.Command) {
|
||||
area.append("\n\n");
|
||||
area.append("(Ring Level:").append(card.getRingLevel()).append(")");
|
||||
area.append("(Ring Level: ").append(card.getRingLevel()).append(")");
|
||||
}
|
||||
|
||||
// Text on gameplay trackers (e.g. Speed)
|
||||
if (StringUtils.isNotEmpty(card.getOverlayText())) {
|
||||
area.append("\n\n");
|
||||
area.append(String.format("(%s)", card.getOverlayText()));
|
||||
}
|
||||
|
||||
// sector
|
||||
|
||||
Reference in New Issue
Block a user