Merge remote-tracking branch 'origin/master'

This commit is contained in:
Hythonia
2021-03-26 16:34:34 +01:00
61 changed files with 539 additions and 368 deletions

View File

@@ -30,6 +30,7 @@ public enum ApiType {
Block (BlockEffect.class),
Bond (BondEffect.class),
Branch (BranchEffect.class),
Camouflage (CamouflageEffect.class),
ChangeCombatants (ChangeCombatantsEffect.class),
ChangeTargets (ChangeTargetsEffect.class),
ChangeText (ChangeTextEffect.class),

View File

@@ -0,0 +1,108 @@
package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
public class CamouflageEffect extends SpellAbilityEffect {
private void randomizeBlockers(SpellAbility sa, Combat combat, Player declarer, Player defender, List<Card> attackers, List<CardCollection> blockerPiles) {
CardLists.shuffle(attackers);
for (int i = 0; i < attackers.size(); i++) {
final Card attacker = attackers.get(i);
CardCollection blockers = blockerPiles.get(i);
// Remove all illegal blockers first
for (int j = blockers.size() - 1; j >= 0; j--) {
final Card blocker = blockers.get(j);
if (!CombatUtil.canBlock(attacker, blocker, combat)) {
blockers.remove(j);
}
}
if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.") &&
blockers.size() < defender.getCreaturesInPlay().size() ||
blockers.size() < CombatUtil.needsBlockers(attacker)) {
// If not enough remaining creatures to block, don't add them as blocker
continue;
}
if (attacker.hasKeyword("CantBeBlockedByAmount GT1") && blockers.size() > 1) {
// If no more than one creature can block, order the player to choose one to block
Card chosen = declarer.getController().chooseCardsForEffect(blockers, sa,
Localizer.getInstance().getMessage("lblChooseBlockerForAttacker", attacker.toString()), 1, 1, false, null).get(0);
combat.addBlocker(attacker, chosen);
continue;
}
// Add all remaning blockers normally
for (final Card blocker : blockers) {
combat.addBlocker(attacker, blocker);
}
}
}
@Override
public void resolve(SpellAbility sa) {
Card hostCard = sa.getHostCard();
Player declarer = getDefinedPlayersOrTargeted(sa).get(0);
Player defender = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Defender"), sa).get(0);
Combat combat = hostCard.getGame().getCombat();
List<Card> attackers = combat.getAttackers();
List<CardCollection> blockerPiles = new ArrayList<>();
if (declarer.isAI()) {
// For AI player, just let it declare blockers normally, then randomize it later.
declarer.getController().declareBlockers(defender, combat);
// Remove all blockers first
for (final Card attacker : attackers) {
CardCollection blockers = combat.getBlockers(attacker);
blockerPiles.add(blockers);
for (final Card blocker : blockers) {
combat.removeFromCombat(blocker);
}
}
} else { // Human player
CardCollection pool = new CardCollection(defender.getCreaturesInPlay());
// remove all blockers that can't block
for (final Card blocker : pool) {
if (!CombatUtil.canBlock(blocker)) {
pool.remove(blocker);
}
}
List<Integer> blockedSoFar = new ArrayList<>(Collections.nCopies(pool.size(), 0));
for (int i = 0; i < attackers.size(); i++) {
int size = pool.size();
CardCollection blockers = new CardCollection(declarer.getController().chooseCardsForEffect(
pool, sa, Localizer.getInstance().getMessage("lblChooseBlockersForPile", String.valueOf(i + 1)), 0, size, false, null));
blockerPiles.add(blockers);
// Remove chosen creatures, unless it can block additional attackers
for (final Card blocker : blockers) {
int index = pool.indexOf(blocker);
Integer blockedCount = blockedSoFar.get(index) + 1;
if (!blocker.canBlockAny() && blocker.canBlockAdditional() < blockedCount) {
pool.remove(index);
blockedSoFar.remove(index);
} else {
blockedSoFar.set(index, blockedCount);
}
}
}
}
randomizeBlockers(sa, combat, declarer, defender, attackers, blockerPiles);
}
}

View File

@@ -349,6 +349,9 @@ public class DiscardEffect extends SpellAbilityEffect {
runParams.put(AbilityKey.Cause, sa);
runParams.put(AbilityKey.FirstTime, firstDiscard);
game.getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
if (sa.hasParam("RememberDiscardingPlayers")) {
source.addRemembered(p);
}
}
}

View File

@@ -1247,17 +1247,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
currentState.addTrigger(t);
return t;
}
@Deprecated
public final void removeTrigger(final Trigger t) {
currentState.removeTrigger(t);
}
@Deprecated
public final void removeTrigger(final Trigger t, final CardStateName state) {
getState(state).removeTrigger(t);
}
public final void clearTriggersNew() {
currentState.clearTriggers();
}
public final boolean hasTrigger(final Trigger t) {
return currentState.hasTrigger(t);

View File

@@ -653,7 +653,14 @@ public class PhaseHandler implements java.io.Serializable {
}
if (combat.isPlayerAttacked(p)) {
if (CombatUtil.canBlock(p, combat)) {
whoDeclaresBlockers.getController().declareBlockers(p, combat);
// Replacement effects (for Camouflage)
final Map<AbilityKey, Object> repRunParams = AbilityKey.mapFromAffected(p);
repRunParams.put(AbilityKey.Player, whoDeclaresBlockers);
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.DeclareBlocker, repRunParams);
if (repres == ReplacementResult.NotReplaced) {
// If not replaced, run normal declare blockers
whoDeclaresBlockers.getController().declareBlockers(p, combat);
}
}
}
else { continue; }

View File

@@ -421,6 +421,9 @@ public class Player extends GameEntity implements Comparable<Player> {
public final Player getWeakestOpponent() {
return getOpponents().min(PlayerPredicates.compareByLife());
}
public final Player getStrongestOpponent() {
return getOpponents().max(PlayerPredicates.compareByLife());
}
public boolean isOpponentOf(Player other) {
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);

View File

@@ -0,0 +1,29 @@
package forge.game.replacement;
import java.util.Map;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
public class ReplaceDeclareBlocker extends ReplacementEffect {
public ReplaceDeclareBlocker(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
super(mapParams, host, intrinsic);
}
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
return false;
}
return true;
}
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.DefendingPlayer, runParams.get(AbilityKey.Affected));
// Here the Player is the one who would declare blockers (may be changed by some Card's effect)
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Player));
}
}

View File

@@ -21,6 +21,7 @@ public enum ReplacementType {
CreateToken(ReplaceToken.class),
DamageDone(ReplaceDamage.class),
DealtDamage(ReplaceDealtDamage.class),
DeclareBlocker(ReplaceDeclareBlocker.class),
Destroy(ReplaceDestroy.class),
Discard(ReplaceDiscard.class),
Draw(ReplaceDraw.class),