Skip to content

Commit 7880766

Browse files
authored
Merge branch 'Card-Forge:master' into master
2 parents 734be2b + 74176a4 commit 7880766

264 files changed

Lines changed: 2014 additions & 469 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/Adventure/GAMEPAD.md

Lines changed: 46 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,57 +11,52 @@ https://github.com/Ryochan7/DS4Windows/releases/
1111
HidHide latest:
1212
https://github.com/ViGEm/HidHide/releases
1313

14-
Other XInput Controller should work, XBox or DS4 Preferaably and other similar XInput Controller with comparable button layout. Custom Key Mapping is not yet supported.
14+
Other XInput Controller should work, XBox or DS4 preferably and other similar XInput Controller with comparable button layout. Custom Key Mapping is not yet supported.
1515

1616
Controls:
1717

18-
UIScenes
19-
20-
DPAD Up/Down/Left/Right - for selecting texboxes and textfield, Scroll Up or Down
21-
Button A - Ok, Show Context
22-
Button B - Cancel
23-
Button X - Increase Difficulty in NewGame Plus/Flip Backside Deck Editor
24-
Button Y - Decrease Difficulty in NewGame Plus/Zoom or Text Mode in Deck Editor
25-
Left/Right Shoulder Button - Scroll Up or Scroll Down on some UIScenes
26-
27-
RewardScene
28-
29-
DPAD Left/Right - Selector
30-
Button A - Confirm/Flip Reward
31-
Button B - Show Rewards/Done
32-
Button Y - Show/Hide Zoom Card
33-
34-
TextInput
35-
36-
DPAD Up/Down/Left/Right - Key Selector
37-
Left Shoulder Button - Shift Keys
38-
Right Shoulder Button - Backspace
39-
Button Start - Jump to Ok
40-
Button A - Confirm
41-
Button B - Cancel
42-
43-
World/Gamescene
44-
45-
DPAD Up/Down/Left/Right - Character Movement
46-
Left Analog - Character Movement
47-
Button A - Menu/Confirm
48-
Button B - Statistics/Edit
49-
Button X - Deck Select/Deck Edit
50-
Button Y - Inventory/Rename
51-
52-
Match/Battle
53-
54-
Left Trigger - Play/Draw/OK (Bottom Left Button)
55-
Right Trigger - Keep/Mulligan/Cancel/End Turn/Alpha Strike (Bottom Right Button)
56-
57-
(To select cards on the battlefield, close Zone tabs first (Button B), then use DPAD)
58-
59-
DPAD Up/Down/Left/Right - Selector
60-
Left Shoulder - Player Panel Selector
61-
Right Shoulder - Zone Selector/Show
62-
Left Analog Down - Select Player (current selected panel)
63-
64-
Button A - Confirm
65-
Button B - Cancel/Hide
66-
Button Y - Show Zoom
67-
Button Back - Show Menu Tabs
18+
- UIScenes
19+
- DPAD Up/Down/Left/Right - for selecting texboxes and textfield, Scroll Up or Down
20+
- Button A - Ok, Show Context
21+
- Button B - Cancel
22+
- Button X - Increase Difficulty in NewGame Plus/Flip Backside Deck Editor
23+
- Button Y - Decrease Difficulty in NewGame Plus/Zoom or Text Mode in Deck Editor
24+
- Left/Right Shoulder Button - Scroll Up or Scroll Down on some UIScenes
25+
26+
27+
- RewardScene
28+
- DPAD Left/Right - Selector
29+
- Button A - Confirm/Flip Reward
30+
- Button B - Show Rewards/Done
31+
- Button Y - Show/Hide Zoom Card
32+
33+
34+
- TextInput
35+
- DPAD Up/Down/Left/Right - Key Selector
36+
- Left Shoulder Button - Shift Keys
37+
- Right Shoulder Button - Backspace
38+
- Button Start - Jump to Ok
39+
- Button A - Confirm
40+
- Button B - Cancel
41+
42+
43+
- World/Gamescene
44+
- DPAD Up/Down/Left/Right - Character Movement
45+
- Left Analog - Character Movement
46+
- Button A - Menu/Confirm
47+
- Button B - Statistics/Edit
48+
- Button X - Deck Select/Deck Edit
49+
- Button Y - Inventory/Rename
50+
51+
52+
- Match/Battle
53+
- Left Trigger - Play/Draw/OK (Bottom Left Button)
54+
- Right Trigger - Keep/Mulligan/Cancel/End Turn/Alpha Strike (Bottom Right Button)
55+
- DPAD Up/Down/Left/Right - Selector (To select cards on the battlefield, close Zone tabs first (Button B))
56+
- Left Shoulder - Player Panel Selector
57+
- Right Shoulder - Zone Selector/Show
58+
- Left Analog Down - Select Player (current selected panel)
59+
- Button A - Confirm
60+
- Button B - Cancel/Hide
61+
- Button Y - Show Zoom
62+
- Button Back - Show Menu Tabs

docs/Card-scripting-API/AbilityFactory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ This follows our general approach where we try to find a reasonable middle groun
318318

319319
## Fight
320320

321-
## FlipACoin
321+
## FlipCoin
322322

323323
## Fog
324324
This AF is based on the original *Fog* spell: "Prevent all combat damage that would be dealt this turn." While this could be done with an Effect, the specialized nature of the AI gives it its own AF.

forge-ai/src/main/java/forge/ai/SpellApiToAi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public enum SpellApiToAi {
103103
.put(ApiType.ExchangeZone, ZoneExchangeAi.class)
104104
.put(ApiType.Explore, ExploreAi.class)
105105
.put(ApiType.Fight, FightAi.class)
106-
.put(ApiType.FlipACoin, FlipACoinAi.class)
106+
.put(ApiType.FlipCoin, FlipCoinAi.class)
107107
.put(ApiType.FlipOntoBattlefield, FlipOntoBattlefieldAi.class)
108108
.put(ApiType.Fog, FogAi.class)
109109
.put(ApiType.GainControl, ControlGainAi.class)

forge-ai/src/main/java/forge/ai/ability/CharmAi.java

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
6060
* bonus choice(s) for the AI otherwise it might be too hard to ever fulfil
6161
* minimum choice requirements with canPlayAi() alone.
6262
*/
63-
chosenList = min > 1 ? chooseMultipleOptionsAi(choices, ai, min)
63+
chosenList = min > 1 ? chooseMultipleOptionsAi(sa, choices, ai, min)
6464
: chooseOptionsAi(sa, choices, ai, timingRight, num, min);
6565
}
6666

@@ -92,12 +92,11 @@ protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) {
9292
}
9393

9494
private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, boolean isTrigger, int num, int min) {
95-
List<AbilitySub> chosenList = Lists.newArrayList();
95+
List<AbilitySub> chosen = Lists.newArrayList();
9696
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
9797
// TODO unused for now, the AI doesn't know how to effectively handle repeated choices
9898
boolean allowRepeat = sa.hasParam("CanRepeatModes");
9999

100-
// Pawprint
101100
final int pawprintLimit = sa.hasParam("Pawprint") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Pawprint"), sa) : 0;
102101
if (pawprintLimit > 0) {
103102
// try to pay for the more expensive subs first
@@ -107,6 +106,7 @@ private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choic
107106

108107
// First pass using standard canPlayAi() for good choices
109108
for (AbilitySub sub : choices) {
109+
handleDependentModes(sa, chosen, sub);
110110
sub.setActivatingPlayer(ai);
111111
// TODO refactor to obtain the AiAbilityDecision instead, then we can check all to sort by value
112112
if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
@@ -117,42 +117,45 @@ private List<AbilitySub> chooseOptionsAi(SpellAbility sa, List<AbilitySub> choic
117117
}
118118
pawprintAmount += curPawprintAmount;
119119
}
120-
chosenList.add(sub);
121-
if (chosenList.size() == num) {
120+
chosen.add(sub);
121+
if (chosen.size() == num) {
122122
// maximum choices reached
123-
return chosenList;
123+
break;
124124
}
125125
}
126126
}
127-
if (isTrigger && chosenList.size() < min) {
127+
if (isTrigger && chosen.size() < min) {
128128
// Second pass using doTrigger(false) to fulfill minimum choice
129-
choices.removeAll(chosenList);
129+
choices.removeAll(chosen);
130130
for (AbilitySub sub : choices) {
131+
handleDependentModes(sa, chosen, sub);
131132
if (aic.doTrigger(sub, false)) {
132-
chosenList.add(sub);
133-
if (chosenList.size() == min) {
134-
return chosenList;
133+
chosen.add(sub);
134+
if (chosen.size() == min) {
135+
break;
135136
}
136137
}
137138
}
138139
// Third pass using doTrigger(true) to force fill minimum choices
139-
if (chosenList.size() < min) {
140-
choices.removeAll(chosenList);
140+
if (chosen.size() < min) {
141+
choices.removeAll(chosen);
141142
for (AbilitySub sub : choices) {
143+
handleDependentModes(sa, chosen, sub);
142144
if (aic.doTrigger(sub, true)) {
143-
chosenList.add(sub);
144-
if (chosenList.size() == min) {
145+
chosen.add(sub);
146+
if (chosen.size() == min) {
145147
break;
146148
}
147149
}
148150
}
149151
}
150152
}
151-
if (chosenList.size() < min) {
153+
if (chosen.size() < min) {
152154
// not enough choices
153-
chosenList.clear();
155+
chosen.clear();
154156
}
155-
return chosenList;
157+
sa.setSubAbility(null);
158+
return chosen;
156159
}
157160

158161
private List<AbilitySub> chooseTriskaidekaphobia(List<AbilitySub> choices, final Player ai) {
@@ -242,32 +245,43 @@ else if (aiLife < 13 || ((aiLife - 13) % 2) == 1) {
242245
return chosenList;
243246
}
244247

245-
// Choice selection for charms that require multiple choices (eg. Cryptic Command, DTK commands)
246-
private List<AbilitySub> chooseMultipleOptionsAi(List<AbilitySub> choices, final Player ai, int min) {
248+
// Choice selection for charms that require multiple choices (e.g. Cryptic Command)
249+
private List<AbilitySub> chooseMultipleOptionsAi(SpellAbility sa, List<AbilitySub> choices, final Player ai, int min) {
247250
AbilitySub goodChoice = null;
248-
List<AbilitySub> chosenList = Lists.newArrayList();
251+
List<AbilitySub> chosen = Lists.newArrayList();
249252
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
250253
for (AbilitySub sub : choices) {
254+
handleDependentModes(sa, chosen, sub);
251255
sub.setActivatingPlayer(ai);
252256
// Assign generic good choice to fill up choices if necessary
253257
if ("Good".equals(sub.getParam("AILogic")) && aic.doTrigger(sub, false)) {
254258
goodChoice = sub;
255259
} else if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) {
256-
chosenList.add(sub);
257-
if (chosenList.size() == min) {
260+
chosen.add(sub);
261+
if (chosen.size() == min) {
258262
break; // enough choices
259263
}
260264
}
261265
}
262266
// Add generic good choice if one more choice is needed
263-
if (chosenList.size() == min - 1 && goodChoice != null) {
264-
chosenList.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
267+
if (chosen.size() == min - 1 && goodChoice != null) {
268+
chosen.add(0, goodChoice); // hack to make Dromoka's Command fight targets work
265269
}
266-
if (chosenList.size() != min) {
267-
chosenList.clear();
270+
if (chosen.size() != min) {
271+
chosen.clear();
268272
}
269-
return chosenList;
270-
}
273+
sa.setSubAbility(null);
274+
return chosen;
275+
}
276+
277+
private void handleDependentModes(SpellAbility sa, List<AbilitySub> chosen, AbilitySub sub) {
278+
if (sub.hasParam("TargetUnique") && !chosen.isEmpty()) {
279+
// support "Each mode must target a different..."
280+
sa.setSubAbility(null);
281+
CharmEffect.chainAbilities(sa, chosen);
282+
sa.appendSubAbility(sub);
283+
}
284+
}
271285

272286
@Override
273287
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents, Map<String, Object> params) {

forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -335,17 +335,14 @@ protected AiAbilityDecision checkApiLogic(Player ai, final SpellAbility sa) {
335335
amount = 1; // TODO: improve this to possibly account for some variability depending on the roll outcome (e.g. 4 for 1d8, perhaps)
336336
}
337337

338-
if (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
339-
Combat combat = game.getCombat();
340-
if (sourceName.equals("Psychic Frog")) {
341-
return doCombatAdaptLogic(source, amount, combat);
342-
}
343-
if (sa.hasParam("Adapt")) {
344-
if (!source.canReceiveCounters(CounterEnumType.P1P1) || source.getCounters(CounterEnumType.P1P1) > 0) {
345-
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
346-
}
347-
return doCombatAdaptLogic(source, amount, combat);
348-
}
338+
if (sa.hasParam("Adapt") &&
339+
(!source.canReceiveCounters(CounterEnumType.P1P1) || source.getCounters(CounterEnumType.P1P1) > 0)) {
340+
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
341+
}
342+
343+
if (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS) &&
344+
(sa.hasParam("Adapt") || sourceName.equals("Psychic Frog"))) {
345+
return doCombatAdaptLogic(source, amount, game.getCombat());
349346
}
350347

351348
if ("Fight".equals(logic) || "PowerDmg".equals(logic)) {

forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAb
139139

140140
for (final Player p : players) {
141141
if (sa.canTarget(p)) {
142-
boolean preferred = false;
143-
preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
142+
boolean preferred = (sa.isCurse() && p.isOpponentOf(aiPlayer)) || (!sa.isCurse() && p == aiPlayer);
144143
sa.resetTargets();
145144
sa.getTargets().add(p);
146145
if (preferred) {
@@ -149,9 +148,8 @@ protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAb
149148

150149
if (mandatory) {
151150
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
152-
} else {
153-
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
154151
}
152+
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
155153
}
156154
}
157155
}

forge-ai/src/main/java/forge/ai/ability/DrawAi.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,23 +289,19 @@ private boolean targetAI(final Player ai, final SpellAbility sa, final boolean m
289289
// TODO: if xPaid and one of the below reasons would fail, instead of
290290
// bailing reduce toPay amount to acceptable level
291291
if (sa.usesTargeting()) {
292-
// ability is targeted
293292
sa.resetTargets();
294293

295294
// if it wouldn't draw anything and its not mandatory, skip it
296295
if (numCards == 0 && !mandatory && !drawback) {
297296
return false;
298297
}
299298

300-
// filter player that can be targeted
301299
PlayerCollection players = game.getPlayers().filter(PlayerPredicates.isTargetableBy(sa));
302300

303-
// no targets skip it
304301
if (players.isEmpty()) {
305302
return false;
306303
}
307304

308-
// filter opponents
309305
PlayerCollection opps = players.filter(PlayerPredicates.isOpponentOf(ai));
310306

311307
for (Player oppA : opps) {
@@ -530,7 +526,7 @@ private boolean targetAI(final Player ai, final SpellAbility sa, final boolean m
530526
}
531527
}
532528
return true;
533-
} // drawTargetAI()
529+
}
534530

535531
@Override
536532
protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) {

forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java renamed to forge-ai/src/main/java/forge/ai/ability/FlipCoinAi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import forge.game.player.Player;
1010
import forge.game.spellability.SpellAbility;
1111

12-
public class FlipACoinAi extends SpellAbilityAi {
12+
public class FlipCoinAi extends SpellAbilityAi {
1313

1414
/* (non-Javadoc)
1515
* @see forge.card.abilityfactory.SpellAiLogic#checkApiLogic(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)

forge-ai/src/main/java/forge/ai/ability/TokenAi.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -256,20 +256,21 @@ protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean
256256
if (tgtRoleAura(ai, sa, actualToken, mandatory)) {
257257
// Targeting handled in tgtRoleAura
258258
return new AiAbilityDecision(100, AiPlayDecision.WillPlay);
259-
} else {
260-
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
261259
}
260+
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
262261
}
263262

264-
if (tgt.canOnlyTgtOpponent()) {
263+
if (sa.canTarget(ai)) {
264+
sa.getTargets().add(ai);
265+
} else if (mandatory || tgt.canOnlyTgtOpponent()) {
265266
PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
266-
if (mandatory && targetableOpps.isEmpty()) {
267-
return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi);
267+
if (targetableOpps.isEmpty()) {
268+
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
268269
}
269270
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
270271
sa.getTargets().add(opp);
271272
} else {
272-
sa.getTargets().add(ai);
273+
return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed);
273274
}
274275
}
275276

0 commit comments

Comments
 (0)