foul-magics/Mage.Common/src/main/java/mage/utils/testers/TestableDialogsRunner.java
Oleg Agafonov e866707912 test framework improves:
- now game logs will show stack ability on push and on resolve (before any choices);
- now game logs will show used choices made by cast/activate, setChoice, setMode and addTarget commands (not work for AI tests, part of #13832);
- improved choice logic for modes and yes/not dialogs (now it's use a more strictly checks, use TestPlayer.MODE_SKIP to stop mode selection);
- improved error logs and testable dialogs menu in cheat mode;
2025-08-04 23:53:16 +04:00

209 lines
7.4 KiB
Java

package mage.utils.testers;
import mage.abilities.Ability;
import mage.choices.Choice;
import mage.choices.ChoiceHintType;
import mage.choices.ChoiceImpl;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.*;
import java.util.stream.Collectors;
/**
* Part of testable game dialogs
* <p>
* Helper class to create additional options in cheat menu - allow to test any game dialogs with any settings
* Allow to call game dialogs from you or from your opponent, e.g. from AI player
* <p>
* All existing human's choose dialogs (search by waitForResponse):
* <p>
* Support of single dialogs (can be called inside effects):
* [x] choose(target)
* [x] choose(cards)
* [x] choose(choice)
* [x] chooseTarget(target)
* [x] chooseTarget(cards)
* [x] chooseTargetAmount
* [x] chooseUse
* [x] choosePile
* [x] announceX
* [x] getAmount
* [x] getMultiAmountWithIndividualConstraints
* <p>
* Support of priority dialogs (can be called by game engine, some can be implemented in theory):
* --- priority
* --- playManaHandling
* --- activateSpecialAction
* --- activateAbility
* --- chooseAbilityForCast
* --- chooseLandOrSpellAbility
* --- chooseMode
* --- chooseMulligan
* --- chooseReplacementEffect
* --- chooseTriggeredAbility
* --- selectAttackers
* --- selectBlockers
* --- selectCombatGroup (part of selectBlockers)
* <p>
* Support of outdated dialogs (not used anymore)
* --- announceRepetitions (part of removed macro feature)
*
* @author JayDi85
*/
public class TestableDialogsRunner {
private final Map<Integer, TestableDialog> dialogs = new LinkedHashMap<>();
static final int LAST_SELECTED_GROUP_ID = 997;
static final int LAST_SELECTED_DIALOG_ID = 998;
// for better UX - save last selected options, so can return to it later
// it's ok to have it global, cause test mode for local and single user environment
static String lastSelectedGroup = null;
static TestableDialog lastSelectedDialog = null;
public TestableDialogsRunner() {
ChooseTargetTestableDialog.register(this);
ChooseCardsTestableDialog.register(this);
ChooseUseTestableDialog.register(this);
ChooseChoiceTestableDialog.register(this);
ChoosePileTestableDialog.register(this);
ChooseAmountTestableDialog.register(this);
AnnounceXTestableDialog.register(this);
GetAmountTestableDialog.register(this);
GetMultiAmountTestableDialog.register(this);
}
void registerDialog(TestableDialog dialog) {
Integer regNumber = this.dialogs.size() + 1;
dialog.setRegNumber(regNumber);
this.dialogs.put(regNumber, dialog);
}
public void selectAndShowTestableDialog(Player player, Ability source, Game game, Player opponent) {
// select group or fast links
List<String> groups = this.dialogs.values().stream()
.map(TestableDialog::getGroup)
.distinct()
.sorted()
.collect(Collectors.toList());
Choice choice = prepareSelectGroupChoice(groups);
player.choose(Outcome.Benefit, choice, game);
String needGroup = null;
TestableDialog needDialog = null;
if (choice.getChoiceKey() != null) {
int needIndex = Integer.parseInt(choice.getChoiceKey());
if (needIndex == LAST_SELECTED_GROUP_ID && lastSelectedGroup != null) {
// fast link to group
needGroup = lastSelectedGroup;
} else if (needIndex == LAST_SELECTED_DIALOG_ID && lastSelectedDialog != null) {
// fast link to dialog
needGroup = lastSelectedDialog.getGroup();
needDialog = lastSelectedDialog;
} else if (needIndex < groups.size()) {
// group
needGroup = groups.get(needIndex);
}
}
if (needGroup == null) {
return;
}
// select dialog
if (needDialog == null) {
choice = prepareSelectDialogChoice(needGroup);
player.choose(Outcome.Benefit, choice, game);
if (choice.getChoiceKey() != null) {
int needRegNumber = Integer.parseInt(choice.getChoiceKey());
needDialog = this.dialogs.getOrDefault(needRegNumber, null);
}
}
if (needDialog == null) {
return;
}
// all fine, can show it and finish
lastSelectedGroup = needGroup;
lastSelectedDialog = needDialog;
needDialog.prepare();
needDialog.showDialog(player, source, game, opponent);
needDialog.showResult(player, game);
}
private Choice prepareSelectGroupChoice(List<String> groups) {
// try to choose group or fast links
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose dialogs group to run");
// use min reg number for groups
Map<String, Integer> groupNumber = new HashMap<>();
this.dialogs.values().forEach(dialog -> {
groupNumber.put(dialog.getGroup(), Math.min(groupNumber.getOrDefault(dialog.getGroup(), Integer.MAX_VALUE), dialog.getRegNumber()));
});
// main groups
for (int i = 0; i < groups.size(); i++) {
String group = groups.get(i);
Integer groupMinNumber = groupNumber.getOrDefault(group, 0);
choice.withItem(
String.valueOf(i),
String.format("%02d. %s", groupMinNumber, group),
groupMinNumber,
ChoiceHintType.TEXT,
String.join("<br>", group)
);
}
// fast link to last group
String lastGroupInfo = String.format(" -> last group: %s", lastSelectedGroup == null ? "not used" : lastSelectedGroup);
choice.withItem(
String.valueOf(LAST_SELECTED_GROUP_ID),
lastGroupInfo,
-2,
ChoiceHintType.TEXT,
lastGroupInfo
);
// fast link to last dialog
String lastDialogName = (lastSelectedDialog == null ? "not used" : String.format("%s - %s",
lastSelectedDialog.getName(), lastSelectedDialog.getDescription()));
String lastDialogInfo = String.format(" -> last dialog: %s", lastDialogName);
choice.withItem(
String.valueOf(LAST_SELECTED_DIALOG_ID),
lastDialogInfo,
-1,
ChoiceHintType.TEXT,
lastDialogInfo
);
return choice;
}
private Choice prepareSelectDialogChoice(String needGroup) {
Choice choice = new ChoiceImpl(false);
choice.setMessage("Choose game dialog to run from " + needGroup);
for (TestableDialog dialog : this.dialogs.values()) {
if (!dialog.getGroup().equals(needGroup)) {
continue;
}
String info = String.format("%s - %s - %s", dialog.getGroup(), dialog.getName(), dialog.getDescription());
choice.withItem(
String.valueOf(dialog.getRegNumber()),
String.format("%02d. %s", dialog.getRegNumber(), info),
dialog.getRegNumber(),
ChoiceHintType.TEXT,
String.join("<br>", info)
);
}
return choice;
}
public Collection<TestableDialog> getDialogs() {
return this.dialogs.values();
}
}