Merge branch 'master' into Attractions

# Conflicts:
#	forge-game/src/main/java/forge/game/player/Player.java
This commit is contained in:
Jetz
2024-06-24 20:06:29 -04:00
24 changed files with 133 additions and 161 deletions

View File

@@ -1,42 +0,0 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game;
/**
* The Enum GlobalRuleChange.
*/
public enum GlobalRuleChange {
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat.");
private final String ruleText;
GlobalRuleChange(String text) {
ruleText = text;
}
public static GlobalRuleChange fromString(String text) {
for (final GlobalRuleChange v : GlobalRuleChange.values()) {
if (v.ruleText.compareToIgnoreCase(text) == 0) {
return v;
}
}
throw new RuntimeException("Element " + text + " not found in GlobalRuleChange enum");
}
}

View File

@@ -198,6 +198,9 @@ public class StaticEffect {
p.removeAdditionalVote(getTimestamp());
p.removeAdditionalOptionalVote(getTimestamp());
p.removeAdditionalVillainousChoices(getTimestamp());
p.removeDeclaresAttackers(getTimestamp());
p.removeDeclaresBlockers(getTimestamp());
}
// modify the affected card

View File

@@ -17,7 +17,6 @@
*/
package forge.game;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
@@ -39,12 +38,8 @@ public class StaticEffects {
// **************** StaticAbility system **************************
private final Map<StaticAbility, StaticEffect> staticEffects = Maps.newHashMap();
//Global rule changes
private final Set<GlobalRuleChange> ruleChanges = EnumSet.noneOf(GlobalRuleChange.class);
public final void clearStaticEffects(final Set<Card> affectedCards) {
ruleChanges.clear();
// remove all static effects
for (final StaticEffect se : staticEffects.values()) {
Iterables.addAll(affectedCards, se.remove());
@@ -52,14 +47,6 @@ public class StaticEffects {
this.staticEffects.clear();
}
public void setGlobalRuleChange(final GlobalRuleChange change) {
this.ruleChanges.add(change);
}
public boolean getGlobalRuleChange(final GlobalRuleChange change) {
return this.ruleChanges.contains(change);
}
/**
* Add a static effect to the list of static effects.
*

View File

@@ -67,7 +67,6 @@ public enum ApiType {
DealDamage (DamageDealEffect.class),
DayTime (DayTimeEffect.class),
Debuff (DebuffEffect.class),
DeclareCombatants (DeclareCombatantsEffect.class),
DelayedTrigger (DelayedTriggerEffect.class),
Destroy (DestroyEffect.class),
DestroyAll (DestroyAllEffect.class),

View File

@@ -1,65 +0,0 @@
package forge.game.ability.effects;
import java.util.List;
import forge.GameCommand;
import forge.game.ability.SpellAbilityEffect;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.TextUtil;
public class DeclareCombatantsEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
boolean attackers = sa.hasParam("DeclareAttackers");
boolean blockers = sa.hasParam("DeclareBlockers");
String what = Lang.joinHomogenous(
attackers
? "which creatures attack"
: null,
blockers
? "which creatures block this turn and how those creatures block"
: null
);
String duration = "EndOfTurn".equals(sa.getParam("Until")) ? "turn" : "combat";
return TextUtil.concatWithSpace(Lang.joinHomogenous(tgtPlayers),Lang.joinVerb(tgtPlayers, "choose"),what,"this",TextUtil.addSuffix(duration,"."));
}
@Override
public void resolve(SpellAbility sa) {
List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
final boolean attackers = sa.hasParam("DeclareAttackers");
final boolean blockers = sa.hasParam("DeclareBlockers");
String until = sa.getParam("Until");
boolean untilEoT = "EndOfTurn".equals(until);
for (Player p : tgtPlayers) { // Obviously the last player will be applied
final PhaseHandler ph = p.getGame().getPhaseHandler();
if (attackers) ph.setPlayerDeclaresAttackers(p);
if (blockers) ph.setPlayerDeclaresBlockers(p);
GameCommand removeOverrides = new GameCommand() {
private static final long serialVersionUID = -8064627517852651016L;
@Override
public void run() {
if (attackers) ph.setPlayerDeclaresAttackers(null);
if (blockers) ph.setPlayerDeclaresBlockers(null);
}
};
if (untilEoT)
p.getGame().getEndOfTurn().addUntil(removeOverrides);
else
p.getGame().getEndOfCombat().addUntil(removeOverrides);
}
}
}

View File

@@ -44,8 +44,6 @@ public class RestartGameEffect extends SpellAbilityEffect {
trigHandler.suppressMode(TriggerType.Shuffled);
game.getPhaseHandler().restart();
game.getPhaseHandler().setPlayerDeclaresAttackers(null);
game.getPhaseHandler().setPlayerDeclaresBlockers(null);
game.getUntap().clearCommands();
game.getUpkeep().clearCommands();
game.getEndOfCombat().clearCommands();

View File

@@ -42,6 +42,8 @@ import forge.util.CollectionSuppliers;
import forge.util.TextUtil;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.StopWatch;
import java.util.*;
@@ -81,9 +83,6 @@ public class PhaseHandler implements java.io.Serializable {
private transient Combat combat = null;
private boolean bRepeatCleanup = false;
private transient Player playerDeclaresBlockers = null;
private transient Player playerDeclaresAttackers = null;
/** The need to next phase. */
private boolean givePriorityToPlayer = false;
@@ -524,9 +523,7 @@ public class PhaseHandler implements java.io.Serializable {
}
private void declareAttackersTurnBasedAction() {
final Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost()
? playerTurn
: playerDeclaresAttackers;
final Player whoDeclares = ObjectUtils.firstNonNull(playerTurn.getDeclaresAttackers(), playerTurn);
if (CombatUtil.canAttack(playerTurn)) {
boolean success = false;
@@ -654,10 +651,7 @@ public class PhaseHandler implements java.io.Serializable {
do {
p = game.getNextPlayerAfter(p);
// Apply Odric's effect here
Player whoDeclaresBlockers = playerDeclaresBlockers == null || playerDeclaresBlockers.hasLost() ? p : playerDeclaresBlockers;
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.attackerChoosesBlockers)) {
whoDeclaresBlockers = combat.getAttackingPlayer();
}
Player whoDeclaresBlockers = ObjectUtils.firstNonNull(p.getDeclaresBlockers(), p);
if (combat.isPlayerAttacked(p)) {
if (CombatUtil.canBlock(p, combat)) {
// Replacement effects (for Camouflage)
@@ -1236,14 +1230,6 @@ public class PhaseHandler implements java.io.Serializable {
givePriorityToPlayer = true;
}
public final void setPlayerDeclaresAttackers(Player player) {
playerDeclaresAttackers = player;
}
public final void setPlayerDeclaresBlockers(Player player) {
playerDeclaresBlockers = player;
}
public void endCombat() {
game.getEndOfCombat().executeUntil();
game.getEndOfCombat().executeUntilEndOfPhase(playerTurn);

View File

@@ -196,6 +196,9 @@ public class Player extends GameEntity implements Comparable<Player> {
private SortedSet<Long> controlVotes = Sets.newTreeSet();
private Map<Long, Integer> additionalVillainousChoices = Maps.newHashMap();
private NavigableMap<Long, Player> declaresAttackers = Maps.newTreeMap();
private NavigableMap<Long, Player> declaresBlockers = Maps.newTreeMap();
private final AchievementTracker achievementTracker = new AchievementTracker();
private final PlayerView view;
@@ -906,7 +909,7 @@ public class Player extends GameEntity implements Comparable<Player> {
setCounters(counterName, newValue, null, true);
getGame().addCounterRemovedThisTurn(counterName, this, delta);
/* TODO Run triggers when something cares
int curCounters = oldValue;
for (int i = 0; i < delta && curCounters != 0; i++) {
@@ -2303,7 +2306,7 @@ public class Player extends GameEntity implements Comparable<Player> {
public final void addSpellCastSinceBegOfYourLastTurn(List<Card> spells) {
spellsCastSinceBeginningOfLastTurn.addAll(spells);
}
public final int getSpellsCastThisTurn() {
return spellsCastThisTurn;
}
@@ -3894,4 +3897,30 @@ public class Player extends GameEntity implements Comparable<Player> {
this.visitAttractions(total);
}
public void addDeclaresAttackers(long ts, Player p) {
this.declaresAttackers.put(ts, p);
}
public void removeDeclaresAttackers(long ts) {
this.declaresAttackers.remove(ts);
}
public Player getDeclaresAttackers() {
Map.Entry<Long, Player> e = declaresAttackers.lastEntry();
return e == null ? null : e.getValue();
}
public void addDeclaresBlockers(long ts, Player p) {
this.declaresBlockers.put(ts, p);
}
public void removeDeclaresBlockers(long ts) {
this.declaresBlockers.remove(ts);
}
public Player getDeclaresBlockers() {
Map.Entry<Long, Player> e = declaresBlockers.lastEntry();
return e == null ? null : e.getValue();
}
}

View File

@@ -175,6 +175,10 @@ public class PlayerProperty {
if (!source.getDamageHistory().hasAttackedThisTurn(player)) {
return false;
}
} else if (property.equals("Attacking")) {
if (game.getCombat() == null || !player.equals(game.getCombat().getAttackingPlayer())) {
return false;
}
} else if (property.equals("Defending")) {
if (game.getCombat() == null || !game.getCombat().getAttackersAndDefenders().values().contains(player)) {
return false;

View File

@@ -165,7 +165,8 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
if (hasParam("AddHiddenKeyword") || hasParam("MayPlay")
|| hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")
|| hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")) {
|| hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")
|| hasParam("DeclaresAttackers") || hasParam("DeclaresBlockers")) {
layers.add(StaticAbilityLayer.RULES);
}

View File

@@ -26,7 +26,6 @@ import com.google.common.collect.Maps;
import forge.GameCommand;
import forge.card.*;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.StaticEffect;
import forge.game.StaticEffects;
import forge.game.ability.AbilityUtils;
@@ -142,11 +141,6 @@ public final class StaticAbilityContinuous {
boolean mayPlayGrantZonePermissions = true;
Integer mayPlayLimit = null;
//Global rules changes
if (layer == StaticAbilityLayer.RULES && params.containsKey("GlobalRule")) {
effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule")));
}
if (layer == StaticAbilityLayer.SETPT || layer == StaticAbilityLayer.CHARACTERISTIC) {
if (params.containsKey("SetPower")) {
setP = params.get("SetPower");
@@ -599,6 +593,17 @@ public final class StaticAbilityContinuous {
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
p.addAdditionalVillainousChoices(se.getTimestamp(), add);
}
if (params.containsKey("DeclaresAttackers")) {
PlayerCollection players = AbilityUtils.getDefinedPlayers(hostCard, params.get("DeclaresAttackers"), stAb);
if (!players.isEmpty())
p.addDeclaresAttackers(se.getTimestamp(), players.getFirst());
}
if (params.containsKey("DeclaresBlockers")) {
PlayerCollection players = AbilityUtils.getDefinedPlayers(hostCard, params.get("DeclaresBlockers"), stAb);
if (!players.isEmpty())
p.addDeclaresBlockers(se.getTimestamp(), players.getFirst());
}
}
}