implement [MH3] Nethergoyf, refactor targets usages by game param (#12267)

This commit is contained in:
Susucre 2024-05-21 13:34:38 +02:00 committed by GitHub
parent 88b6f4036f
commit 754b382e78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 592 additions and 285 deletions

View file

@ -67,7 +67,7 @@ public class CollectEvidenceCost extends CostImpl {
// TODO: require target to have minimum selected total mana value (requires refactor)
Target target = new TargetCardInYourGraveyard(1, Integer.MAX_VALUE) {
@Override
public String getMessage() {
public String getMessage(Game game) {
// shows selected mana value
int totalMV = this
.getTargets()
@ -76,7 +76,7 @@ public class CollectEvidenceCost extends CostImpl {
.filter(Objects::nonNull)
.mapToInt(MageObject::getManaValue)
.sum();
return super.getMessage() + HintUtils.prepareText(
return super.getMessage(game) + HintUtils.prepareText(
" (selected mana value " + totalMV + " of " + amount + ")",
totalMV >= amount ? Color.GREEN : Color.RED
);

View file

@ -31,7 +31,7 @@ public class ExileTargetCost extends CostImpl {
this.text = "exile " + target.getDescription();
}
public ExileTargetCost(ExileTargetCost cost) {
protected ExileTargetCost(ExileTargetCost cost) {
super(cost);
for (Permanent permanent : cost.permanents) {
this.permanents.add(permanent.copy());

View file

@ -75,7 +75,7 @@ public class RollPlanarDieEffect extends OneShotEffect {
}
boolean done = false;
while (controller.canRespond() && effect != null && !done) {
if (target != null && !target.isChosen() && target.canChoose(controller.getId(), source, game)) {
if (target != null && !target.isChosen(game) && target.canChoose(controller.getId(), source, game)) {
controller.chooseTarget(Outcome.Benefit, target, source, game);
source.addTarget(target);
}

View file

@ -27,6 +27,7 @@ public class SacrificeAllEffect extends OneShotEffect {
/**
* Each player sacrifices a permanent
*
* @param filter can be generic, will automatically add article and necessary sacrifice predicates
*/
public SacrificeAllEffect(FilterPermanent filter) {
@ -35,6 +36,7 @@ public class SacrificeAllEffect extends OneShotEffect {
/**
* Each player sacrifices N permanents
*
* @param filter can be generic, will automatically add necessary sacrifice predicates
*/
public SacrificeAllEffect(int amount, FilterPermanent filter) {
@ -43,6 +45,7 @@ public class SacrificeAllEffect extends OneShotEffect {
/**
* Each player sacrifices X permanents
*
* @param filter can be generic, will automatically add necessary sacrifice predicates
*/
public SacrificeAllEffect(DynamicValue amount, FilterPermanent filter) {
@ -91,7 +94,7 @@ public class SacrificeAllEffect extends OneShotEffect {
continue;
}
TargetSacrifice target = new TargetSacrifice(numTargets, filter);
while (!target.isChosen() && target.canChoose(player.getId(), source, game) && player.canRespond()) {
while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) {
player.choose(Outcome.Sacrifice, target, source, game);
}
perms.addAll(target.getTargets());

View file

@ -64,7 +64,7 @@ public class SacrificeEffect extends OneShotEffect {
continue;
}
TargetSacrifice target = new TargetSacrifice(amount, filter);
while (!target.isChosen() && target.canChoose(player.getId(), source, game) && player.canRespond()) {
while (!target.isChosen(game) && target.canChoose(player.getId(), source, game) && player.canRespond()) {
player.choose(Outcome.Sacrifice, target, source, game);
}
for (UUID targetId : target.getTargets()) {

View file

@ -151,7 +151,7 @@ class CrewCost extends CostImpl {
}
Target target = new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE, filter, true) {
@Override
public String getMessage() {
public String getMessage(Game game) {
// shows selected power
int selectedPower = this.targets.keySet().stream()
.map(game::getPermanent)
@ -162,7 +162,7 @@ class CrewCost extends CostImpl {
if (selectedPower >= value) {
extraInfo = HintUtils.prepareText(extraInfo, Color.GREEN);
}
return super.getMessage() + " " + extraInfo;
return super.getMessage(game) + " " + extraInfo;
}
};

View file

@ -32,10 +32,14 @@ public class EscapeAbility extends SpellAbility {
private final String staticText;
public EscapeAbility(Card card, String manaCost, int exileCount) {
this(card, manaCost, exileCount, new CostsImpl<>());
this(card, manaCost, new CostsImpl<>(), exileCount);
}
public EscapeAbility(Card card, String manaCost, int exileCount, Costs<Cost> additionalCosts) {
public EscapeAbility(Card card, String manaCost, Costs<Cost> additionalCost) {
this(card, manaCost, additionalCost, 0);
}
public EscapeAbility(Card card, String manaCost, Costs<Cost> additionalCosts, int exileCount) {
super(card.getSpellAbility());
this.newId();
this.setCardName(card.getName() + " with Escape");
@ -45,17 +49,22 @@ public class EscapeAbility extends SpellAbility {
this.clearManaCosts();
this.clearManaCostsToPay();
String text = "Escape&mdash;" + manaCost;
this.addCost(new ManaCostsImpl<>(manaCost));
for (Cost cost : additionalCosts) {
text += ", " + CardUtil.getTextWithFirstCharUpperCase(cost.getText());
this.addCost(cost.copy().setText("")); // hide additional cost text from rules
}
if (exileCount > 0) {
this.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(exileCount, filter), "")); // hide additional cost text from rules
}
text += ", Exile " + CardUtil.numberToText(exileCount) + " other cards from your graveyard."
+ " <i>(You may cast this card from your graveyard for its escape cost.)</i>";
this.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(exileCount, filter), "")); // hide additional cost text from rules
String text = "Escape&mdash;" + manaCost;
for (Cost cost : additionalCosts) {
text += ", " + CardUtil.getTextWithFirstCharUpperCase(cost.getText());
}
if (exileCount > 0) {
text += ", Exile " + CardUtil.numberToText(exileCount) + " other cards from your graveyard";
}
text += ". <i>(You may cast this card from your graveyard for its escape cost.)</i>";
this.staticText = text;
}

View file

@ -177,7 +177,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
&& player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + '?', source, game)) {
Target target = new TargetSacrifice(filter);
player.choose(Outcome.Sacrifice, target, source, game);
if (!target.isChosen()) {
if (!target.isChosen(game)) {
return false;
}
game.getState().setValue("offering_" + card.getId(), true);

View file

@ -113,7 +113,7 @@ class SaddleCost extends CostImpl {
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
Target target = new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE, filter, true) {
@Override
public String getMessage() {
public String getMessage(Game game) {
// shows selected power
int selectedPower = this.targets.keySet().stream()
.map(game::getPermanent)
@ -125,7 +125,7 @@ class SaddleCost extends CostImpl {
if (selectedPower >= value) {
extraInfo = HintUtils.prepareText(extraInfo, Color.GREEN);
}
return super.getMessage() + " " + extraInfo;
return super.getMessage(game) + " " + extraInfo;
}
};

View file

@ -4349,14 +4349,14 @@ public abstract class PlayerImpl implements Player, Serializable {
List<Ability> options = new ArrayList<>();
if (ability.isModal()) {
addModeOptions(options, ability, game);
} else if (!ability.getTargets().getUnchosen().isEmpty()) {
} else if (!ability.getTargets().getUnchosen(game).isEmpty()) {
// TODO: Handle other variable costs than mana costs
if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
addVariableXOptions(options, ability, 0, game);
} else {
addTargetOptions(options, ability, 0, game);
}
} else if (!ability.getCosts().getTargets().getUnchosen().isEmpty()) {
} else if (!ability.getCosts().getTargets().getUnchosen(game).isEmpty()) {
addCostTargetOptions(options, ability, 0, game);
}
@ -4371,13 +4371,13 @@ public abstract class PlayerImpl implements Player, Serializable {
newOption.getModes().clearSelectedModes();
newOption.getModes().addSelectedMode(mode.getId());
newOption.getModes().setActiveMode(mode);
if (!newOption.getTargets().getUnchosen().isEmpty()) {
if (!newOption.getTargets().getUnchosen(game).isEmpty()) {
if (!newOption.getManaCosts().getVariableCosts().isEmpty()) {
addVariableXOptions(options, newOption, 0, game);
} else {
addTargetOptions(options, newOption, 0, game);
}
} else if (!newOption.getCosts().getTargets().getUnchosen().isEmpty()) {
} else if (!newOption.getCosts().getTargets().getUnchosen(game).isEmpty()) {
addCostTargetOptions(options, newOption, 0, game);
} else {
options.add(newOption);
@ -4390,7 +4390,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
protected void addTargetOptions(List<Ability> options, Ability option, int targetNum, Game game) {
for (Target target : option.getTargets().getUnchosen().get(targetNum).getTargetOptions(option, game)) {
for (Target target : option.getTargets().getUnchosen(game).get(targetNum).getTargetOptions(option, game)) {
Ability newOption = option.copy();
if (target instanceof TargetAmount) {
for (UUID targetId : target.getTargets()) {

View file

@ -19,9 +19,9 @@ import java.util.UUID;
*/
public interface Target extends Serializable {
boolean isChosen();
boolean isChosen(Game game);
boolean doneChoosing();
boolean doneChoosing(Game game);
void clearChosen();
@ -98,7 +98,10 @@ public interface Target extends Serializable {
*/
String getDescription();
String getMessage();
/**
* @return message displayed on choosing targets (can be dynamically changed on more target selected)
*/
String getMessage(Game game);
/**
* @return single target name

View file

@ -44,12 +44,12 @@ public abstract class TargetAmount extends TargetImpl {
}
@Override
public boolean isChosen() {
return doneChoosing();
public boolean isChosen(Game game) {
return doneChoosing(game);
}
@Override
public boolean doneChoosing() {
public boolean doneChoosing(Game game) {
return amountWasSet
&& (remainingAmount == 0
|| (getMinNumberOfTargets() < getMaxNumberOfTargets()
@ -104,7 +104,7 @@ public abstract class TargetAmount extends TargetImpl {
if (!amountWasSet) {
setAmount(source, game);
}
chosen = isChosen();
chosen = isChosen(game);
while (remainingAmount > 0) {
if (!player.canRespond()) {
return chosen;
@ -112,7 +112,7 @@ public abstract class TargetAmount extends TargetImpl {
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
return chosen;
}
chosen = isChosen();
chosen = isChosen(game);
}
return chosen;
}

View file

@ -143,7 +143,7 @@ public abstract class TargetImpl implements Target {
}
@Override
public String getMessage() {
public String getMessage(Game game) {
// UI choose message
String suffix = "";
if (this.chooseHint != null) {
@ -215,7 +215,7 @@ public abstract class TargetImpl implements Target {
}
@Override
public boolean isChosen() {
public boolean isChosen(Game game) {
if (getMaxNumberOfTargets() == 0 && getNumberOfTargets() == 0) {
return true;
}
@ -223,7 +223,7 @@ public abstract class TargetImpl implements Target {
}
@Override
public boolean doneChoosing() {
public boolean doneChoosing(Game game) {
return getMaxNumberOfTargets() != 0 && targets.size() == getMaxNumberOfTargets();
}
@ -332,7 +332,7 @@ public abstract class TargetImpl implements Target {
return chosen;
}
chosen = targets.size() >= getNumberOfTargets();
} while (!isChosen() && !doneChoosing());
} while (!isChosen(game) && !doneChoosing(game));
return chosen;
}
@ -375,7 +375,7 @@ public abstract class TargetImpl implements Target {
}
}
chosen = targets.size() >= getNumberOfTargets();
} while (!isChosen() && !doneChoosing());
} while (!isChosen(game) && !doneChoosing(game));
return chosen;
}

View file

@ -37,8 +37,8 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
return this;
}
public List<Target> getUnchosen() {
return stream().filter(target -> !target.isChosen()).collect(Collectors.toList());
public List<Target> getUnchosen(Game game) {
return stream().filter(target -> !target.isChosen(game)).collect(Collectors.toList());
}
public void clearChosen() {
@ -47,8 +47,8 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
}
}
public boolean isChosen() {
return stream().allMatch(Target::isChosen);
public boolean isChosen(Game game) {
return stream().allMatch(t -> t.isChosen(game));
}
public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Ability source, Game game) {
@ -56,8 +56,8 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
if (!canChoose(playerId, source, game)) {
return false;
}
while (!isChosen()) {
Target target = this.getUnchosen().get(0);
while (!isChosen(game)) {
Target target = this.getUnchosen(game).get(0);
if (!target.choose(outcome, playerId, sourceId, source, game)) {
return false;
}
@ -73,8 +73,8 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
}
//int state = game.bookmarkState();
while (!isChosen()) {
Target target = this.getUnchosen().get(0);
while (!isChosen(game)) {
Target target = this.getUnchosen(game).get(0);
UUID targetController = playerId;
// some targets can have controller different than ability controller
@ -97,7 +97,7 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
return false;
}
// Check if there are some rules for targets are violated, if so reset the targets and start again
if (this.getUnchosen().isEmpty()
if (this.getUnchosen(game).isEmpty()
&& game.replaceEvent(new GameEvent(GameEvent.EventType.TARGETS_VALID, source.getSourceId(), source, source.getControllerId()), source)) {
//game.restoreState(state, "Targets");
clearChosen();

View file

@ -85,7 +85,7 @@ public class TargetCardInLibrary extends TargetCard {
return chosen;
}
chosen = targets.size() >= getMinNumberOfTargets();
} while (!isChosen() && !doneChoosing());
} while (!isChosen(game) && !doneChoosing(game));
return chosen;
}