mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +00:00
Codechanges and lots of fixes related to playing other player's cards from exile.
- Cards now keep track of who's allowed to cast them, fixing possible issues in multiplayer. - Face-down cards can now be properly looked at when allowed to by a static ability. - Improve support for casting a face-down exiled card in general. - Add Shared Fate (including AI support).
This commit is contained in:
@@ -29,6 +29,7 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPlayOption;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
@@ -143,20 +144,37 @@ public final class GameActionUtil {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getAlternativeCosts.
|
||||
* Find the alternative costs to a {@link SpellAbility}.
|
||||
* </p>
|
||||
*
|
||||
* @param sa
|
||||
* a SpellAbility.
|
||||
* @return an ArrayList<SpellAbility>.
|
||||
* get alternative costs as additional spell abilities
|
||||
* a {@link SpellAbility}.
|
||||
* @param activator
|
||||
* the {@link Player} for which to calculate available
|
||||
* @return a {@link List} of {@link SpellAbility} objects, each representing
|
||||
* a possible alternative cost the provided activator can use to pay
|
||||
* the provided {@link SpellAbility}.
|
||||
*/
|
||||
public static final ArrayList<SpellAbility> getAlternativeCosts(SpellAbility sa) {
|
||||
ArrayList<SpellAbility> alternatives = new ArrayList<SpellAbility>();
|
||||
Card source = sa.getHostCard();
|
||||
public static final List<SpellAbility> getAlternativeCosts(final SpellAbility sa, final Player activator) {
|
||||
final List<SpellAbility> alternatives = new ArrayList<SpellAbility>();
|
||||
if (!sa.isBasicSpell()) {
|
||||
return alternatives;
|
||||
}
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final CardPlayOption playOption = source.mayPlay(activator);
|
||||
if (sa.isSpell() && playOption != null && playOption.isWithoutManaCost()) {
|
||||
final SpellAbility newSA = sa.copy();
|
||||
final SpellAbilityRestriction sar = new SpellAbilityRestriction();
|
||||
sar.setVariables(sa.getRestrictions());
|
||||
sar.setZone(null);
|
||||
newSA.setRestrictions(sar);
|
||||
newSA.setBasicSpell(false);
|
||||
newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
|
||||
newSA.setDescription(sa.getDescription() + " (without paying its mana cost)");
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
|
||||
for (final String keyword : source.getKeywords()) {
|
||||
if (sa.isSpell() && keyword.startsWith("Flashback")) {
|
||||
final SpellAbility flashback = sa.copy();
|
||||
@@ -183,18 +201,6 @@ public final class GameActionUtil {
|
||||
newSA.setDescription(sa.getDescription() + " (without paying its mana cost)");
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
if (sa.isSpell() && keyword.equals("May be played by your opponent without paying its mana cost")) {
|
||||
final SpellAbility newSA = sa.copy();
|
||||
SpellAbilityRestriction sar = new SpellAbilityRestriction();
|
||||
sar.setVariables(sa.getRestrictions());
|
||||
sar.setZone(null);
|
||||
sar.setOpponentOnly(true);
|
||||
newSA.setRestrictions(sar);
|
||||
newSA.setBasicSpell(false);
|
||||
newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
|
||||
newSA.setDescription(sa.getDescription() + " (without paying its mana cost)");
|
||||
alternatives.add(newSA);
|
||||
}
|
||||
if (sa.isSpell() && keyword.startsWith("May be played without paying its mana cost and as though it has flash")) {
|
||||
final SpellAbility newSA = sa.copy();
|
||||
SpellAbilityRestriction sar = new SpellAbilityRestriction();
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StaticEffects {
|
||||
boolean setPT = false;
|
||||
String[] addHiddenKeywords = null;
|
||||
String addColors = null;
|
||||
boolean removeMayLookAt = false;
|
||||
boolean removeMayLookAt = false, removeMayPlay = false;
|
||||
|
||||
if (params.containsKey("ChangeColorWordsTo")) {
|
||||
changeColorWordsTo = params.get("ChangeColorWordsTo");
|
||||
@@ -158,6 +158,9 @@ public class StaticEffects {
|
||||
if (params.containsKey("MayLookAt")) {
|
||||
removeMayLookAt = true;
|
||||
}
|
||||
if (params.containsKey("MayPlay")) {
|
||||
removeMayPlay = true;
|
||||
}
|
||||
|
||||
if (params.containsKey("IgnoreEffectCost")) {
|
||||
for (final SpellAbility s : se.getSource().getSpellAbilities()) {
|
||||
@@ -254,6 +257,9 @@ public class StaticEffects {
|
||||
if (removeMayLookAt) {
|
||||
affectedCard.setMayLookAt(controller, false);
|
||||
}
|
||||
if (removeMayPlay) {
|
||||
affectedCard.removeMayPlay(controller);
|
||||
}
|
||||
}
|
||||
se.clearTimestamps();
|
||||
return affectedCards;
|
||||
|
||||
@@ -112,8 +112,6 @@ public class EffectEffect extends SpellAbilityEffect {
|
||||
eff.setImmutable(true);
|
||||
eff.setEffectSource(hostCard);
|
||||
|
||||
// Effects should be Orange or something probably
|
||||
|
||||
final Card e = eff;
|
||||
|
||||
// Grant SVars first in order to give references to granted abilities
|
||||
|
||||
@@ -136,6 +136,8 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
|
||||
// if this card is an Aura, what Entity is it enchanting?
|
||||
private GameEntity enchanting = null;
|
||||
|
||||
private final Map<Player, CardPlayOption> mayPlay = Maps.newTreeMap();
|
||||
|
||||
// changes by AF animate and continuous static effects - timestamp is the key of maps
|
||||
private Map<Long, CardChangedType> changedCardTypes = new ConcurrentSkipListMap<Long, CardChangedType>();
|
||||
private Map<Long, KeywordsChange> changedCardKeywords = new ConcurrentSkipListMap<Long, KeywordsChange>();
|
||||
@@ -1503,10 +1505,22 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
|
||||
public String getAbilityText() {
|
||||
return getAbilityText(currentState);
|
||||
}
|
||||
public String getAbilityText(CardState state) {
|
||||
public String getAbilityText(final CardState state) {
|
||||
final CardTypeView type = state.getType();
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!mayPlay.isEmpty()) {
|
||||
sb.append("May be played by: ");
|
||||
sb.append(Lang.joinHomogenous(mayPlay.entrySet(), new Function<Entry<Player, CardPlayOption>, String>() {
|
||||
@Override public String apply(final Entry<Player, CardPlayOption> entry) {
|
||||
return entry.getKey().toString() + entry.getValue().toString();
|
||||
}
|
||||
}));
|
||||
sb.append("\r\n");
|
||||
}
|
||||
|
||||
if (type.isInstant() || type.isSorcery()) {
|
||||
final StringBuilder sb = abilityTextInstantSorcery(state);
|
||||
sb.append(abilityTextInstantSorcery(state));
|
||||
|
||||
if (haunting != null) {
|
||||
sb.append("Haunting: ").append(haunting);
|
||||
@@ -1520,8 +1534,6 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
|
||||
return sb.toString().replaceAll("CARDNAME", state.getName());
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (monstrous) {
|
||||
sb.append("Monstrous\r\n");
|
||||
}
|
||||
@@ -2141,6 +2153,17 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
|
||||
view.setPlayerMayLook(player, mayLookAt, temp);
|
||||
}
|
||||
|
||||
public final CardPlayOption mayPlay(final Player player) {
|
||||
return mayPlay.get(player);
|
||||
}
|
||||
public final void setMayPlay(final Player player, final boolean withoutManaCost, final boolean ignoreColor) {
|
||||
final CardPlayOption option = this.mayPlay.get(player);
|
||||
this.mayPlay.put(player, option == null ? new CardPlayOption(withoutManaCost, ignoreColor) : option.add(withoutManaCost, ignoreColor));
|
||||
}
|
||||
public final void removeMayPlay(final Player player) {
|
||||
this.mayPlay.remove(player);
|
||||
}
|
||||
|
||||
public final CardCollectionView getEquippedBy(boolean allowModify) {
|
||||
return CardCollection.getView(equippedBy, allowModify);
|
||||
}
|
||||
@@ -6246,13 +6269,18 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<SpellAbility> getAllPossibleAbilities(Player player, boolean removeUnplayable) {
|
||||
public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) {
|
||||
// this can only be called by the Human
|
||||
final List<SpellAbility> abilities = new ArrayList<SpellAbility>();
|
||||
for (SpellAbility sa : getSpellAbilities()) {
|
||||
//add alternative costs as additional spell abilities
|
||||
abilities.add(sa);
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa));
|
||||
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
}
|
||||
if (isFaceDown() && isInZone(ZoneType.Exile) && mayPlay(player) != null) {
|
||||
for (final SpellAbility sa : getState(CardStateName.Original).getSpellAbilities()) {
|
||||
abilities.add(sa);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = abilities.size() - 1; i >= 0; i--) {
|
||||
@@ -6266,7 +6294,7 @@ public class Card extends GameEntity implements Comparable<Card>, IIdentifiable
|
||||
}
|
||||
}
|
||||
|
||||
if (isLand() && player.canPlayLand(this)) {
|
||||
if (getState(CardStateName.Original).getType().isLand() && player.canPlayLand(this)) {
|
||||
Ability.PLAY_LAND_SURROGATE.setHostCard(this);
|
||||
abilities.add(Ability.PLAY_LAND_SURROGATE);
|
||||
}
|
||||
|
||||
36
forge-game/src/main/java/forge/game/card/CardPlayOption.java
Normal file
36
forge-game/src/main/java/forge/game/card/CardPlayOption.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package forge.game.card;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public final class CardPlayOption {
|
||||
private final boolean withoutManaCost, ignoreManaCostColor;
|
||||
|
||||
public CardPlayOption(final boolean withoutManaCost, final boolean ignoreManaCostColor) {
|
||||
this.withoutManaCost = withoutManaCost;
|
||||
this.ignoreManaCostColor = ignoreManaCostColor;
|
||||
}
|
||||
|
||||
public CardPlayOption add(final boolean withoutManaCost, final boolean ignoreManaCostColor) {
|
||||
return new CardPlayOption(isWithoutManaCost() || withoutManaCost, isIgnoreManaCostColor() || ignoreManaCostColor);
|
||||
}
|
||||
|
||||
public boolean isWithoutManaCost() {
|
||||
return withoutManaCost;
|
||||
}
|
||||
|
||||
public boolean isIgnoreManaCostColor() {
|
||||
return ignoreManaCostColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isWithoutManaCost()) {
|
||||
return " (without paying its mana cost)";
|
||||
}
|
||||
if (isIgnoreManaCostColor()) {
|
||||
return " (may spend mana as though it were mana of any color to cast it)";
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package forge.game.card;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ImageKeys;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.CardEdition;
|
||||
@@ -102,6 +105,9 @@ public class CardView extends GameEntityView {
|
||||
void updateZone(Card c) {
|
||||
set(TrackableProperty.Zone, c.getZone() == null ? null : c.getZone().getZoneType());
|
||||
}
|
||||
public boolean isInZone(final Iterable<ZoneType> zones) {
|
||||
return Iterables.contains(zones, getZone());
|
||||
}
|
||||
|
||||
public boolean isCloned() {
|
||||
return get(TrackableProperty.Cloned);
|
||||
@@ -397,11 +403,17 @@ public class CardView extends GameEntityView {
|
||||
}
|
||||
|
||||
//if viewer is controlled by another player, also check if face can be shown to that player
|
||||
PlayerView mindSlaveMaster = viewer.getMindSlaveMaster();
|
||||
final PlayerView mindSlaveMaster = viewer.getMindSlaveMaster();
|
||||
if (mindSlaveMaster != null && canFaceDownBeShownTo(mindSlaveMaster)) {
|
||||
return true;
|
||||
}
|
||||
return !getController().isOpponentOf(viewer) || getCurrentState().getOpponentMayLook();
|
||||
if (isInZone(EnumSet.of(ZoneType.Battlefield, ZoneType.Stack, ZoneType.Sideboard)) && getController().equals(viewer)) {
|
||||
return true;
|
||||
}
|
||||
if (getController().isOpponentOf(viewer) && getCurrentState().getOpponentMayLook()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public CardView getEquipping() {
|
||||
|
||||
@@ -1386,6 +1386,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
// Dakkon Blackblade Avatar will use a similar effect
|
||||
if (canPlayLand(land, ignoreZoneAndTiming)) {
|
||||
land.setController(this, 0);
|
||||
if (land.isFaceDown()) {
|
||||
land.turnFaceUp();
|
||||
}
|
||||
game.getAction().moveTo(getZone(ZoneType.Battlefield), land);
|
||||
|
||||
// play a sound
|
||||
@@ -1407,7 +1410,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public final boolean canPlayLand(final Card land) {
|
||||
return canPlayLand(land, false);
|
||||
}
|
||||
public final boolean canPlayLand(Card land, final boolean ignoreZoneAndTiming) {
|
||||
public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTiming) {
|
||||
if (!ignoreZoneAndTiming && !canCastSorcery()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1423,14 +1426,13 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
}
|
||||
|
||||
if (land != null && !ignoreZoneAndTiming) {
|
||||
if (land.getOwner() != this && !land.hasKeyword("May be played by your opponent"))
|
||||
return false;
|
||||
|
||||
if (land.getOwner() == this && land.hasKeyword("May be played by your opponent") && !land.hasKeyword("May be played"))
|
||||
final boolean mayPlay = land.mayPlay(this) != null;
|
||||
if (land.getOwner() != this && !mayPlay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Zone zone = game.getZoneOf(land);
|
||||
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !land.hasStartOfKeyword("May be played")))) {
|
||||
if (zone != null && (zone.is(ZoneType.Battlefield) || (!zone.is(ZoneType.Hand) && !(mayPlay || land.hasStartOfKeyword("May be played"))))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package forge.game.spellability;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -91,7 +92,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
||||
}
|
||||
|
||||
// for uncastables like lotus bloom, check if manaCost is blank (except for morph spells)
|
||||
if (!isCastFaceDown() && isBasicSpell() && card.getManaCost().isNoCost()) {
|
||||
if (!isCastFaceDown() && isBasicSpell() && card.getState(card.isFaceDown() ? CardStateName.Original : card.getCurrentStateName()).getManaCost().isNoCost()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -200,9 +200,8 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return true;
|
||||
}
|
||||
|
||||
Player activator = sa.getActivatingPlayer();
|
||||
|
||||
Zone cardZone = activator.getGame().getZoneOf(c);
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
final Zone cardZone = activator.getGame().getZoneOf(c);
|
||||
if (cardZone == null || !cardZone.is(this.getZone())) {
|
||||
// If Card is not in the default activating zone, do some additional checks
|
||||
// Not a Spell, or on Battlefield, return false
|
||||
@@ -211,12 +210,12 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return false;
|
||||
}
|
||||
if (cardZone.is(ZoneType.Stack)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
if (c.hasKeyword("May be played") && activator.equals(c.getController())) {
|
||||
if (c.mayPlay(activator) != null) {
|
||||
return true;
|
||||
}
|
||||
if (c.hasKeyword("May be played by your opponent") && !activator.equals(c.getController())) {
|
||||
if (c.hasKeyword("May be played") && activator.equals(c.getController())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -296,7 +295,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sa.isSpell() && activator.isOpponentOf(c.getController()) && c.hasKeyword("May be played by your opponent")) {
|
||||
if (sa.isSpell() && c.mayPlay(activator) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,9 +257,8 @@ public class StaticAbility extends CardTraitBase {
|
||||
return in;
|
||||
}
|
||||
|
||||
// apply the ability if it has the right mode
|
||||
/**
|
||||
* Apply ability.
|
||||
* Apply ability if it has the right mode.
|
||||
*
|
||||
* @param mode
|
||||
* the mode
|
||||
|
||||
@@ -113,6 +113,7 @@ public class StaticAbilityContinuous {
|
||||
boolean removeSubTypes = false;
|
||||
boolean removeCreatureTypes = false;
|
||||
boolean controllerMayLookAt = false;
|
||||
boolean controllerMayPlay = false, mayPlayWithoutManaCost = false, mayPlayIgnoreColor = false;
|
||||
|
||||
//Global rules changes
|
||||
if (params.containsKey("GlobalRule")) {
|
||||
@@ -328,6 +329,14 @@ public class StaticAbilityContinuous {
|
||||
if (params.containsKey("MayLookAt")) {
|
||||
controllerMayLookAt = true;
|
||||
}
|
||||
if (params.containsKey("MayPlay")) {
|
||||
controllerMayPlay = true;
|
||||
if (params.containsKey("MayPlayWithoutManaCost")) {
|
||||
mayPlayWithoutManaCost = true;
|
||||
} else if (params.containsKey("MayPlayIgnoreColor")) {
|
||||
mayPlayIgnoreColor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.containsKey("IgnoreEffectCost")) {
|
||||
String cost = params.get("IgnoreEffectCost");
|
||||
@@ -558,8 +567,11 @@ public class StaticAbilityContinuous {
|
||||
if (controllerMayLookAt) {
|
||||
affectedCard.setMayLookAt(controller, true);
|
||||
}
|
||||
if (controllerMayPlay) {
|
||||
affectedCard.setMayPlay(controller, mayPlayWithoutManaCost, mayPlayIgnoreColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return affectedCards;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,14 +39,17 @@ import forge.game.zone.ZoneType;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimaps;
|
||||
|
||||
public class TriggerHandler {
|
||||
private final ArrayList<TriggerType> suppressedModes = new ArrayList<TriggerType>();
|
||||
private final ArrayList<Trigger> activeTriggers = new ArrayList<Trigger>();
|
||||
private final List<TriggerType> suppressedModes = Collections.synchronizedList(new ArrayList<TriggerType>());
|
||||
private final List<Trigger> activeTriggers = Collections.synchronizedList(new ArrayList<Trigger>());
|
||||
|
||||
private final ArrayList<Trigger> delayedTriggers = new ArrayList<Trigger>();
|
||||
private final ArrayListMultimap<Player, Trigger> playerDefinedDelayedTriggers = ArrayListMultimap.create();
|
||||
private final List<TriggerWaiting> waitingTriggers = new ArrayList<TriggerWaiting>();
|
||||
private final List<Trigger> delayedTriggers = Collections.synchronizedList(new ArrayList<Trigger>());
|
||||
private final ListMultimap<Player, Trigger> playerDefinedDelayedTriggers = Multimaps.synchronizedListMultimap(ArrayListMultimap.<Player, Trigger>create());
|
||||
private final List<TriggerWaiting> waitingTriggers = Collections.synchronizedList(new ArrayList<TriggerWaiting>());
|
||||
private final Game game;
|
||||
|
||||
public TriggerHandler(Game gameState) {
|
||||
@@ -268,7 +271,7 @@ public class TriggerHandler {
|
||||
boolean checkStatics = false;
|
||||
|
||||
// Static triggers
|
||||
for (final Trigger t : activeTriggers) {
|
||||
for (final Trigger t : Lists.newArrayList(activeTriggers)) {
|
||||
if (t.isStatic() && canRunTrigger(t, mode, runParams)) {
|
||||
runSingleTrigger(t, runParams);
|
||||
checkStatics = true;
|
||||
|
||||
@@ -216,9 +216,11 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
|
||||
if (sp.isSpell()) {
|
||||
source.setController(activator, 0);
|
||||
Spell spell = (Spell) sp;
|
||||
final Spell spell = (Spell) sp;
|
||||
if (spell.isCastFaceDown()) {
|
||||
source.turnFaceDown();
|
||||
} else if (source.isFaceDown()) {
|
||||
source.turnFaceUp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ public enum TrackableProperty {
|
||||
ChosenDirection(TrackableTypes.EnumType(Direction.class)),
|
||||
Remembered(TrackableTypes.StringType),
|
||||
NamedCard(TrackableTypes.StringType),
|
||||
PlayerMayLook(TrackableTypes.PlayerViewCollectionType),
|
||||
PlayerMayLookTemp(TrackableTypes.PlayerViewCollectionType),
|
||||
PlayerMayLook(TrackableTypes.PlayerViewCollectionType, false),
|
||||
PlayerMayLookTemp(TrackableTypes.PlayerViewCollectionType, false),
|
||||
Equipping(TrackableTypes.CardViewType),
|
||||
EquippedBy(TrackableTypes.CardViewCollectionType),
|
||||
Enchanting(TrackableTypes.GameEntityViewType),
|
||||
|
||||
Reference in New Issue
Block a user