Vote: replace hidden keywords with timestamped properties

This commit is contained in:
Hans Mackowiak
2020-03-19 06:22:44 +00:00
committed by Michael Kamensky
parent d35a2cdead
commit 33d83dc0c5
20 changed files with 258 additions and 82 deletions

View File

@@ -2323,7 +2323,7 @@ public class ComputerUtil {
return chosen;
}
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes) {
public static Object vote(Player ai, List<Object> options, SpellAbility sa, Multimap<Object, Player> votes, Player forPlayer) {
final Card source = sa.getHostCard();
final Player controller = source.getController();
final Game game = controller.getGame();

View File

@@ -513,8 +513,8 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
return ComputerUtil.vote(player, options, sa, votes);
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
return ComputerUtil.vote(player, options, sa, votes, forPlayer);
}
@Override

View File

@@ -46,6 +46,12 @@ public class VoteAi extends SpellAbilityAi {
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
if (params.containsKey("Voter")) {
Player p = (Player)params.get("Voter");
if (p.isOpponentOf(player)) {
return min;
}
}
if (sa.getActivatingPlayer().isOpponentOf(player)) {
return min;
}

View File

@@ -914,4 +914,17 @@ public class Game {
}
return false;
}
public Player getControlVote() {
Player result = null;
long maxValue = 0;
for (Player p : getPlayers()) {
Long v = p.getHighestControlVote();
if (v != null && v > maxValue) {
maxValue = v;
result = p;
}
}
return result;
}
}

View File

@@ -6,12 +6,12 @@
* 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/>.
*/
@@ -37,7 +37,7 @@ import com.google.common.collect.Maps;
* <p>
* StaticEffect class.
* </p>
*
*
* @author Forge
* @version $Id$
*/
@@ -72,7 +72,7 @@ public class StaticEffect {
/**
* setTimestamp TODO Write javadoc for this method.
*
*
* @param t
* a long
*/
@@ -82,7 +82,7 @@ public class StaticEffect {
/**
* getTimestamp. TODO Write javadoc for this method.
*
*
* @return a long
*/
public final long getTimestamp() {
@@ -93,7 +93,7 @@ public class StaticEffect {
* <p>
* Getter for the field <code>source</code>.
* </p>
*
*
* @return a {@link forge.game.card.Card} object.
*/
public final Card getSource() {
@@ -104,7 +104,7 @@ public class StaticEffect {
* <p>
* Getter for the field <code>affectedCards</code>.
* </p>
*
*
* @return a {@link forge.CardList} object.
*/
public final CardCollectionView getAffectedCards() {
@@ -115,7 +115,7 @@ public class StaticEffect {
* <p>
* Setter for the field <code>affectedCards</code>.
* </p>
*
*
* @param list
* a {@link forge.CardList} object.
*/
@@ -125,7 +125,7 @@ public class StaticEffect {
/**
* Gets the affected players.
*
*
* @return the affected players
*/
public final List<Player> getAffectedPlayers() {
@@ -134,7 +134,7 @@ public class StaticEffect {
/**
* Sets the affected players.
*
*
* @param list
* the new affected players
*/
@@ -144,7 +144,7 @@ public class StaticEffect {
/**
* setParams. TODO Write javadoc for this method.
*
*
* @param params
* a HashMap
*/
@@ -154,7 +154,7 @@ public class StaticEffect {
/**
* Gets the params.
*
*
* @return the params
*/
public final Map<String, String> getParams() {
@@ -171,13 +171,12 @@ public class StaticEffect {
/**
* Undo everything that was changed by this effect.
*
*
* @return a {@link CardCollectionView} of all affected cards.
*/
final CardCollectionView remove() {
final CardCollectionView affectedCards = getAffectedCards();
final List<Player> affectedPlayers = getAffectedPlayers();
//final Map<String, String> params = getParams();
String changeColorWordsTo = null;
@@ -245,6 +244,10 @@ public class StaticEffect {
p.removeMaxLandPlays(getTimestamp());
p.removeMaxLandPlaysInfinite(getTimestamp());
p.removeControlVote(getTimestamp());
p.removeAdditionalVote(getTimestamp());
p.removeAdditionalOptionalVote(getTimestamp());
}
// modify the affected card

View File

@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -9,9 +10,9 @@ import forge.game.ability.AbilityKey;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
@@ -19,10 +20,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
@@ -67,33 +65,29 @@ public class VoteEffect extends SpellAbilityEffect {
return;
}
// starting with the activator
int pSize = tgtPlayers.size();
Player activator = sa.getActivatingPlayer();
while (tgtPlayers.contains(activator) && !activator.equals(Iterables.getFirst(tgtPlayers, null))) {
tgtPlayers.add(pSize - 1, tgtPlayers.remove(0));
// starting with the activator
int aidx = tgtPlayers.indexOf(activator);
if (aidx != -1) {
Collections.rotate(tgtPlayers, -aidx);
}
ListMultimap<Object, Player> votes = ArrayListMultimap.create();
Player voter = null;
PlayerCollection voters = game.getPlayers().filter(PlayerPredicates.hasKeyword("You choose how each player votes this turn."));
if (voters.size() > 1) {
List<Card> illusions = CardLists.filter(voters.getCardsIn(ZoneType.Command), CardPredicates.nameEquals("Illusion of Choice Effect"));
voter = Collections.max(illusions, CardPredicates.compareByTimestamp()).getController();
} else if (voters.size() == 1) {
voter = voters.get(0);
}
Player voter = game.getControlVote();
for (final Player p : tgtPlayers) {
int voteAmount = p.getKeywords().getAmount("You get an additional vote.") + 1;
int optionalVotes = p.getKeywords().getAmount("You may vote an additional time.");
voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes);
int voteAmount = p.getAdditionalVotesAmount() + 1;
int optionalVotes = p.getAdditionalOptionalVotesAmount();
Player realVoter = voter == null ? p : voter;
Map<String, Object> params = Maps.newHashMap();
params.put("Voter", realVoter);
voteAmount += p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyAdditionalVotesDoYouWant"), 0, optionalVotes, params);
for (int i = 0; i < voteAmount; i++) {
Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes);
Object result = realVoter.getController().vote(sa, host + Localizer.getInstance().getMessage("lblVote") + ":", voteType, votes, p);
votes.put(result, p);
host.getGame().getAction().nofityOfValue(sa, p, result + "\r\n" + Localizer.getInstance().getMessage("lblCurrentVote") + ":" + votes, p);
@@ -104,34 +98,49 @@ public class VoteEffect extends SpellAbilityEffect {
runParams.put(AbilityKey.AllVotes, votes);
game.getTriggerHandler().runTrigger(TriggerType.Vote, runParams, false);
List<String> subAbs = Lists.newArrayList();
final List<Object> mostVotes = getMostVotes(votes);
if (sa.hasParam("Tied") && mostVotes.size() > 1) {
subAbs.add(sa.getParam("Tied"));
} else if (sa.hasParam("VoteSubAbility")) {
for (final Object o : mostVotes) {
host.addRemembered(o);
}
subAbs.add(sa.getParam("VoteSubAbility"));
} else {
for (Object type : mostVotes) {
subAbs.add(sa.getParam("Vote" + type.toString()));
}
}
if (sa.hasParam("StoreVoteNum")) {
for (final Object type : voteType) {
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
}
} else {
for (final String subAb : subAbs) {
final SpellAbility action = AbilityFactory.getAbility(host.getSVar(subAb), host);
if (sa.hasParam("EachVote")) {
for (Map.Entry<Object, Collection<Player>> e : votes.asMap().entrySet()) {
final SpellAbility action = AbilityFactory.getAbility(host, sa.getParam("Vote" + e.getKey().toString()));
action.setActivatingPlayer(sa.getActivatingPlayer());
((AbilitySub) action).setParent(sa);
AbilityUtils.resolve(action);
for (Player p : e.getValue()) {
host.addRemembered(p);
AbilityUtils.resolve(action);
host.removeRemembered(p);
}
}
} else {
List<String> subAbs = Lists.newArrayList();
final List<Object> mostVotes = getMostVotes(votes);
if (sa.hasParam("Tied") && mostVotes.size() > 1) {
subAbs.add(sa.getParam("Tied"));
} else if (sa.hasParam("VoteSubAbility")) {
for (final Object o : mostVotes) {
host.addRemembered(o);
}
subAbs.add(sa.getParam("VoteSubAbility"));
} else {
for (Object type : mostVotes) {
subAbs.add(sa.getParam("Vote" + type.toString()));
}
}
if (sa.hasParam("StoreVoteNum")) {
for (final Object type : voteType) {
host.setSVar("VoteNum" + type, "Number$" + votes.get(type).size());
}
} else {
for (final String subAb : subAbs) {
final SpellAbility action = AbilityFactory.getAbility(host, subAb);
action.setActivatingPlayer(sa.getActivatingPlayer());
((AbilitySub) action).setParent(sa);
AbilityUtils.resolve(action);
}
}
if (sa.hasParam("VoteSubAbility")) {
host.clearRemembered();
}
}
if (sa.hasParam("VoteSubAbility")) {
host.clearRemembered();
}
}

View File

@@ -3938,7 +3938,9 @@ public class Card extends GameEntity implements Comparable<Card> {
keywordsGrantedByTextChanges.add(newKw);
}
}
addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true);
if (!addKeywords.isEmpty() || !removeKeywords.isEmpty()) {
addChangedCardKeywordsInternal(addKeywords, removeKeywords, false, false, timestamp, true);
}
}
private void updateKeywordsOnRemoveChangedText(final KeywordsChange k) {

View File

@@ -162,6 +162,10 @@ public class Player extends GameEntity implements Comparable<Player> {
private Card blessingEffect = null;
private Card keywordEffect = null;
private Map<Long, Integer> additionalVotes = Maps.newHashMap();
private Map<Long, Integer> additionalOptionalVotes = Maps.newHashMap();
private SortedSet<Long> controlVotes = Sets.newTreeSet();
private final AchievementTracker achievementTracker = new AchievementTracker();
private final PlayerView view;
@@ -3067,4 +3071,86 @@ public class Player extends GameEntity implements Comparable<Player> {
this.updateZoneForView(com);
return keywordEffect;
}
public void addAdditionalVote(long timestamp, int value) {
additionalVotes.put(timestamp, value);
getView().updateAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
public void removeAdditionalVote(long timestamp) {
if (additionalVotes.remove(timestamp) != null) {
getView().updateAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
}
public int getAdditionalVotesAmount() {
int value = 0;
for (Integer i : additionalVotes.values()) {
value += i;
}
return value;
}
public void addAdditionalOptionalVote(long timestamp, int value) {
additionalOptionalVotes.put(timestamp, value);
getView().updateOptionalAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
public void removeAdditionalOptionalVote(long timestamp) {
if (additionalOptionalVotes.remove(timestamp) != null) {
getView().updateOptionalAdditionalVote(this);
getGame().fireEvent(new GameEventPlayerStatsChanged(this, false));
}
}
public int getAdditionalOptionalVotesAmount() {
int value = 0;
for (Integer i : additionalOptionalVotes.values()) {
value += i;
}
return value;
}
public boolean addControlVote(long timestamp) {
if (controlVotes.add(timestamp)) {
updateControlVote();
return true;
}
return false;
}
public boolean removeControlVote(long timestamp) {
if (controlVotes.remove(timestamp)) {
updateControlVote();
return true;
}
return false;
}
void updateControlVote() {
// need to update all players because it can't know
Player control = getGame().getControlVote();
for (Player pl : getGame().getPlayers()) {
pl.getView().updateControlVote(pl.equals(control));
getGame().fireEvent(new GameEventPlayerStatsChanged(pl, false));
}
}
public Set<Long> getControlVote() {
return controlVotes;
}
public void setControlVote(Set<Long> value) {
controlVotes.clear();
controlVotes.addAll(value);
updateControlVote();
}
public Long getHighestControlVote() {
if (controlVotes.isEmpty()) {
return null;
}
return controlVotes.last();
}
}

View File

@@ -166,7 +166,7 @@ public abstract class PlayerController {
return chooseSomeType(kindOfType, sa, validTypes, invalidTypes, false);
}
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes);
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer);
public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question);
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);

View File

@@ -271,6 +271,27 @@ public class PlayerView extends GameEntityView {
set(TrackableProperty.NumDrawnThisTurn, p.getNumDrawnThisTurn());
}
public int getAdditionalVote() {
return get(TrackableProperty.AdditionalVote);
}
public void updateAdditionalVote(Player p) {
set(TrackableProperty.AdditionalVote, p.getAdditionalVotesAmount());
}
public int getOptionalAdditionalVote() {
return get(TrackableProperty.OptionalAdditionalVote);
}
public void updateOptionalAdditionalVote(Player p) {
set(TrackableProperty.OptionalAdditionalVote, p.getAdditionalOptionalVotesAmount());
}
public boolean getControlVote() {
return get(TrackableProperty.ControlVotes);
}
public void updateControlVote(boolean val) {
set(TrackableProperty.ControlVotes, val);
}
public ImmutableMultiset<String> getKeywords() {
return get(TrackableProperty.Keywords);
}
@@ -497,6 +518,19 @@ public class PlayerView extends GameEntityView {
details.add(Localizer.getInstance().getMessage("lblCardDrawnThisTurnHas", String.valueOf(getNumDrawnThisTurn())));
details.add(Localizer.getInstance().getMessage("lblDamagepreventionHas", String.valueOf(getPreventNextDamage())));
int v = getAdditionalVote();
if (v > 0) {
details.add(Localizer.getInstance().getMessage("lblAdditionalVotes", String.valueOf(v)));
}
v = getOptionalAdditionalVote();
if (v > 0) {
details.add(Localizer.getInstance().getMessage("lblOptionalAdditionalVotes", String.valueOf(v)));
}
if (getControlVote()) {
details.add(Localizer.getInstance().getMessage("lblControlsVote"));
}
if (getIsExtraTurn()) {
details.add(Localizer.getInstance().getMessage("lblIsExtraTurn"));
}

View File

@@ -181,15 +181,9 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
layers.add(StaticAbilityLayer.MODIFYPT);
}
if (hasParam("AddHiddenKeyword")) {
layers.add(StaticAbilityLayer.RULES);
}
if (hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")) {
layers.add(StaticAbilityLayer.RULES);
}
if (hasParam("AdjustLandPlays")) {
if (hasParam("AddHiddenKeyword")
|| hasParam("IgnoreEffectCost") || hasParam("Goad") || hasParam("CanBlockAny") || hasParam("CanBlockAmount")
|| hasParam("AdjustLandPlays") || hasParam("ControlVote") || hasParam("AdditionalVote") || hasParam("AdditionalOptionalVote")) {
layers.add(StaticAbilityLayer.RULES);
}

View File

@@ -513,6 +513,20 @@ public final class StaticAbilityContinuous {
}
}
if (params.containsKey("ControlVote")) {
p.addControlVote(se.getTimestamp());
}
if (params.containsKey("AdditionalVote")) {
String mhs = params.get("AdditionalVote");
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
p.addAdditionalVote(se.getTimestamp(), add);
}
if (params.containsKey("AdditionalOptionalVote")) {
String mhs = params.get("AdditionalOptionalVote");
int add = AbilityUtils.calculateAmount(hostCard, mhs, stAb);
p.addAdditionalOptionalVote(se.getTimestamp(), add);
}
if (params.containsKey("RaiseMaxHandSize")) {
String rmhs = params.get("RaiseMaxHandSize");
int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb);

View File

@@ -134,6 +134,9 @@ public enum TrackableProperty {
HasUnlimitedLandPlay(TrackableTypes.BooleanType),
NumLandThisTurn(TrackableTypes.IntegerType),
NumDrawnThisTurn(TrackableTypes.IntegerType),
AdditionalVote(TrackableTypes.IntegerType),
OptionalAdditionalVote(TrackableTypes.IntegerType),
ControlVotes(TrackableTypes.BooleanType),
Keywords(TrackableTypes.KeywordCollectionViewType, FreezeMode.IgnoresFreeze),
Commander(TrackableTypes.CardViewCollectionType, FreezeMode.IgnoresFreeze),
CommanderCast(TrackableTypes.IntegerMapType),

View File

@@ -486,7 +486,7 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes) {
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
return chooseItem(options);
}

View File

@@ -2,5 +2,5 @@ Name:Ballot Broker
ManaCost:2 W
Types:Creature Human Advisor
PT:2/3
S:Mode$ Continuous | Affected$ You | AddKeyword$ You may vote an additional time. | Description$ While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
Oracle:While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
S:Mode$ Continuous | Affected$ You | AdditionalOptionalVote$ 1 | Description$ While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)
Oracle:While voting, you may vote an additional time. (The votes can be for different choices or for the same choice.)

View File

@@ -2,6 +2,6 @@ Name:Brago's Representative
ManaCost:2 W
Types:Creature Human Advisor
PT:1/4
S:Mode$ Continuous | Affected$ You | AddKeyword$ You get an additional vote. | Description$ While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)
S:Mode$ Continuous | Affected$ You | AdditionalVote$ 1 | Description$ While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)
SVar:Picture:http://www.wizards.com/global/images/magic/general/bragos_representative.jpg
Oracle:While voting, you get an additional vote. (The votes can be for different choices or for the same choice.)

View File

@@ -0,0 +1,9 @@
Name:Expropriate
ManaCost:7 U U
Types:Sorcery
A:SP$ Vote | Cost$ 7 U U | Defined$ Player | VoteType$ Time,Money | VoteTime$ DBTime | VoteMoney$ DBMoney | EachVote$ True | SubAbility$ DBChange | SpellDescription$ Councils dilemma — Starting with you, each player votes for time or money. For each time vote, take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate.
SVar:DBTime:DB$ AddTurn | Defined$ You | NumTurns$ 1
SVar:DBMoney:DB$ ChooseCard | Defined$ You | Choices$ Permanent.RememberedPlayerOwn | SubAbility$ DBControl
SVar:DBControl:DB$ GainControl | Defined$ ChosenCard | NewController$ You
SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ None
Oracle:Councils dilemma — Starting with you, each player votes for time or money. For each time vote, take an extra turn after this one. For each money vote, choose a permanent owned by the voter and gain control of it. Exile Expropriate.

View File

@@ -3,6 +3,6 @@ ManaCost:U
Types:Instant
A:SP$ Effect | Cost$ U | Name$ Illusion of Choice Effect | StaticAbilities$ STVoter | SubAbility$ DBDraw | SpellDescription$ You choose how each player votes this turn. Draw a card.
SVar:DBDraw:DB$ Draw | NumCards$ 1
SVar:STVoter:Mode$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ You choose how each player votes this turn. | Description$ You choose how each player votes this turn.
SVar:STVoter:Mode$ Continuous | EffectZone$ Command | Affected$ You | ControlVote$ True | Description$ You choose how each player votes this turn.
AI:RemoveDeck:All
Oracle:You choose how each player votes this turn. Draw a card.
Oracle:You choose how each player votes this turn. Draw a card.

View File

@@ -2010,6 +2010,9 @@ lblDamagepreventionHas=Damage prevention: {0}
lblIsExtraTurn=Extra Turn: Yes
lblExtraTurnCountHas=Extra Turn Count: {0}
lblAntedHas=Ante''d: {0}
lblAdditionalVotes=You get {0} additional votes.
lblOptionalAdditionalVotes=You may vote {0} additional times.
lblControlsVote=You choose how each player votes.
#VStack.java
lblAlwaysYes=Always Yes
lblAlwaysNo=Always No

View File

@@ -1181,7 +1181,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
@Override
public Object vote(final SpellAbility sa, final String prompt, final List<Object> options,
final ListMultimap<Object, Player> votes) {
final ListMultimap<Object, Player> votes, Player forPlayer) {
return getGui().one(prompt, options);
}