mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Merge branch 'fixes' into 'master'
Fixes See merge request core-developers/forge!276
This commit is contained in:
@@ -170,8 +170,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public static boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
public static boolean matchesValid(final Object o, final String[] valids, final Card srcCard) {
|
||||||
if (o instanceof GameEntity) {
|
if (o instanceof GameObject) {
|
||||||
final GameEntity c = (GameEntity) o;
|
final GameObject c = (GameObject) o;
|
||||||
return c.isValid(valids, srcCard.getController(), srcCard, null);
|
return c.isValid(valids, srcCard.getController(), srcCard, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,18 @@ public class ForgeScript {
|
|||||||
if (!sa.hasParam("Equip")) {
|
if (!sa.hasParam("Equip")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (property.startsWith("IsTargeting")) {
|
||||||
|
String k[] = property.split(" ", 2);
|
||||||
|
boolean found = false;
|
||||||
|
for (GameObject o : AbilityUtils.getDefinedObjects(source, k[1], spellAbility)) {
|
||||||
|
if (sa.isTargeting(o)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1205,7 +1205,7 @@ public class AbilityUtils {
|
|||||||
final SpellAbility sa) {
|
final SpellAbility sa) {
|
||||||
final FCollection<SpellAbility> sas = new FCollection<SpellAbility>();
|
final FCollection<SpellAbility> sas = new FCollection<SpellAbility>();
|
||||||
final String defined = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
|
final String defined = (def == null) ? "Self" : applyAbilityTextChangeEffects(def, sa); // default to Self
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
final Game game = card.getGame();
|
||||||
|
|
||||||
SpellAbility s = null;
|
SpellAbility s = null;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import forge.game.card.Card;
|
|||||||
import forge.game.event.GameEventCardModeChosen;
|
import forge.game.event.GameEventCardModeChosen;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -34,14 +33,16 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
|||||||
final SpellAbility fallback = sa.getAdditionalAbility("FallbackAbility");
|
final SpellAbility fallback = sa.getAdditionalAbility("FallbackAbility");
|
||||||
|
|
||||||
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
|
|
||||||
for (final Player p : tgtPlayers) {
|
for (final Player p : tgtPlayers) {
|
||||||
// determine if any of the choices are not valid
|
// determine if any of the choices are not valid
|
||||||
List<SpellAbility> saToRemove = Lists.<SpellAbility>newArrayList();
|
List<SpellAbility> saToRemove = Lists.<SpellAbility>newArrayList();
|
||||||
|
|
||||||
for (SpellAbility saChoice : abilities) {
|
for (SpellAbility saChoice : abilities) {
|
||||||
if ("Player.IsRemembered".equals(saChoice.getParam("Defined")) && saChoice.hasParam("UnlessCost")) {
|
if (!saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer()) ) {
|
||||||
|
saToRemove.add(saChoice);
|
||||||
|
} else if (saChoice.hasParam("UnlessCost") &&
|
||||||
|
"Player.IsRemembered".equals(saChoice.getParam("Defined"))) {
|
||||||
String unlessCost = saChoice.getParam("UnlessCost");
|
String unlessCost = saChoice.getParam("UnlessCost");
|
||||||
// Sac a permanent in presence of Sigarda, Host of Herons
|
// Sac a permanent in presence of Sigarda, Host of Herons
|
||||||
// TODO: generalize this by testing if the unless cost can be paid
|
// TODO: generalize this by testing if the unless cost can be paid
|
||||||
@@ -55,7 +56,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
abilities.removeAll(saToRemove);
|
abilities.removeAll(saToRemove);
|
||||||
|
|
||||||
if (tgt != null && sa.getTargets().isTargeting(p) && !p.canBeTargetedBy(sa)) {
|
if (sa.usesTargeting() && sa.getTargets().isTargeting(p) && !p.canBeTargetedBy(sa)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
// set the host card for copied spellabilities
|
// set the host card for copied spellabilities
|
||||||
for (final SpellAbility newSa : tgtCard.getSpellAbilities()) {
|
for (final SpellAbility newSa : tgtCard.getSpellAbilities()) {
|
||||||
newSa.setHostCard(cardToCopy);
|
newSa.setOriginalHost(cardToCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore name if it should be unchanged
|
// restore name if it should be unchanged
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public abstract class RegenerateBaseEffect extends SpellAbilityEffect {
|
|||||||
final Game game = hostCard.getGame();
|
final Game game = hostCard.getGame();
|
||||||
|
|
||||||
// create Effect for Regeneration
|
// create Effect for Regeneration
|
||||||
Card eff = createEffect(
|
final Card eff = createEffect(
|
||||||
hostCard, sa.getActivatingPlayer(), hostCard.getName() + "'s Regeneration", hostCard.getImageKey());
|
hostCard, sa.getActivatingPlayer(), hostCard.getName() + "'s Regeneration", hostCard.getImageKey());
|
||||||
|
|
||||||
eff.addRemembered(list);
|
eff.addRemembered(list);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class SetStateEffect extends SpellAbilityEffect {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("Transform".equals(mode) && tgt.equals(host)) {
|
if ("Transform".equals(mode) && tgt.equals(host) && sa.hasSVar("StoredTransform")) {
|
||||||
// If want to Transform, and host is trying to transform self, skip if not in alignment
|
// If want to Transform, and host is trying to transform self, skip if not in alignment
|
||||||
boolean skip = tgt.getTransformedTimestamp() != Long.parseLong(sa.getSVar("StoredTransform"));
|
boolean skip = tgt.getTransformedTimestamp() != Long.parseLong(sa.getSVar("StoredTransform"));
|
||||||
// Clear SVar from SA so it doesn't get reused accidentally
|
// Clear SVar from SA so it doesn't get reused accidentally
|
||||||
|
|||||||
@@ -18,9 +18,7 @@
|
|||||||
package forge.game.ability.effects;
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
@@ -281,10 +279,10 @@ public class TokenEffect extends SpellAbilityEffect {
|
|||||||
if (prototype == null) {
|
if (prototype == null) {
|
||||||
tokens = tokenInfo.makeTokenWithMultiplier(controller, finalAmount, cause != null);
|
tokens = tokenInfo.makeTokenWithMultiplier(controller, finalAmount, cause != null);
|
||||||
grantHiddenKeywords(tokens);
|
grantHiddenKeywords(tokens);
|
||||||
grantSvars(tokens, root);
|
grantSvars(tokens, sa);
|
||||||
grantAbilities(tokens, root);
|
grantAbilities(tokens, sa);
|
||||||
grantTriggers(tokens, root);
|
grantTriggers(tokens, sa);
|
||||||
grantStatics(tokens, root);
|
grantStatics(tokens, sa);
|
||||||
} else {
|
} else {
|
||||||
tokens = TokenInfo.makeTokensFromPrototype(prototype, controller, finalAmount, cause != null);
|
tokens = TokenInfo.makeTokensFromPrototype(prototype, controller, finalAmount, cause != null);
|
||||||
}
|
}
|
||||||
@@ -352,7 +350,6 @@ public class TokenEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String determineTokenColor(Card host) {
|
private String determineTokenColor(Card host) {
|
||||||
Set<String> colorSet = new HashSet<>();
|
|
||||||
final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length);
|
final String[] substitutedColors = Arrays.copyOf(this.tokenColors, this.tokenColors.length);
|
||||||
for (int i = 0; i < substitutedColors.length; i++) {
|
for (int i = 0; i < substitutedColors.length; i++) {
|
||||||
if (substitutedColors[i].equals("ChosenColor")) {
|
if (substitutedColors[i].equals("ChosenColor")) {
|
||||||
|
|||||||
@@ -2350,11 +2350,12 @@ public class CardFactoryUtil {
|
|||||||
final String name = StringUtils.join(k);
|
final String name = StringUtils.join(k);
|
||||||
|
|
||||||
final String trigStr = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield "
|
final String trigStr = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield "
|
||||||
+ " | Execute$ " + name + "Choose | ValidCard$ Card.Self | Secondary$ True"
|
+ " | ValidCard$ Card.Self | Secondary$ True"
|
||||||
+ " | TriggerDescription$ Fabricate " + n + " (" + inst.getReminderText() + ")";
|
+ " | TriggerDescription$ Fabricate " + n + " (" + inst.getReminderText() + ")";
|
||||||
|
|
||||||
final String choose = "DB$ GenericChoice | Choices$ DB" + name + "Counter,DB" + name + "Token | ConditionPresent$ Card.StrictlySelf | SubAbility$ DB" + name + "Token2 | AILogic$ " + name;
|
final String choose = "DB$ GenericChoice | AILogic$ " + name;
|
||||||
final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + n + " | SpellDescription$ Put "
|
final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + n +
|
||||||
|
" | IsPresent$ Card.StrictlySelf | SpellDescription$ Put "
|
||||||
+ Lang.nounWithNumeral(n, "+1/+1 counter") + " on it.";
|
+ Lang.nounWithNumeral(n, "+1/+1 counter") + " on it.";
|
||||||
final String token = "DB$ Token | TokenAmount$ " + n + " | TokenName$ Servo | TokenTypes$ Artifact,Creature,Servo"
|
final String token = "DB$ Token | TokenAmount$ " + n + " | TokenName$ Servo | TokenTypes$ Artifact,Creature,Servo"
|
||||||
+ " | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 1 | TokenToughness$ 1"
|
+ " | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 1 | TokenToughness$ 1"
|
||||||
@@ -2363,10 +2364,15 @@ public class CardFactoryUtil {
|
|||||||
|
|
||||||
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||||
|
|
||||||
card.setSVar(name + "Choose", choose);
|
SpellAbility saChoose = AbilityFactory.getAbility(choose, card);
|
||||||
card.setSVar("DB" + name + "Counter", counter);
|
|
||||||
card.setSVar("DB" + name + "Token", token);
|
List<AbilitySub> list = Lists.newArrayList();
|
||||||
card.setSVar("DB" + name + "Token2", token + " | ConditionPresent$ Card.StrictlySelf | ConditionCompare$ EQ0");
|
list.add((AbilitySub)AbilityFactory.getAbility(counter, card));
|
||||||
|
list.add((AbilitySub)AbilityFactory.getAbility(token, card));
|
||||||
|
saChoose.setAdditionalAbilityList("Choices", list);
|
||||||
|
saChoose.setIntrinsic(intrinsic);
|
||||||
|
|
||||||
|
trigger.setOverridingAbility(saChoose);
|
||||||
|
|
||||||
inst.addTrigger(trigger);
|
inst.addTrigger(trigger);
|
||||||
} else if (keyword.startsWith("Fading")) {
|
} else if (keyword.startsWith("Fading")) {
|
||||||
@@ -3367,6 +3373,8 @@ public class CardFactoryUtil {
|
|||||||
|
|
||||||
// extra part for the Damage Prevention keywords
|
// extra part for the Damage Prevention keywords
|
||||||
if (keyword.startsWith("Prevent all ")) {
|
if (keyword.startsWith("Prevent all ")) {
|
||||||
|
// TODO add intrinsic warning
|
||||||
|
|
||||||
boolean isCombat = false;
|
boolean isCombat = false;
|
||||||
boolean from = false;
|
boolean from = false;
|
||||||
boolean to = false;
|
boolean to = false;
|
||||||
|
|||||||
@@ -816,6 +816,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
if (manaPart != null) {
|
if (manaPart != null) {
|
||||||
clone.manaPart = new AbilityManaPart(host, mapParams);
|
clone.manaPart = new AbilityManaPart(host, mapParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear maps for copy, the values will be added later
|
||||||
|
clone.additionalAbilities = Maps.newHashMap();
|
||||||
|
clone.additionalAbilityLists = Maps.newHashMap();
|
||||||
// run special copy Ability to make a deep copy
|
// run special copy Ability to make a deep copy
|
||||||
CardFactory.copySpellAbility(this, clone, host, lki);
|
CardFactory.copySpellAbility(this, clone, host, lki);
|
||||||
} catch (final CloneNotSupportedException e) {
|
} catch (final CloneNotSupportedException e) {
|
||||||
@@ -1486,24 +1490,33 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
return topSA.getHostCard().isValid(tgt.getValidTgts(), getActivatingPlayer(), getHostCard(), this);
|
return topSA.getHostCard().isValid(tgt.getValidTgts(), getActivatingPlayer(), getHostCard(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTargeting(GameObject o) {
|
||||||
|
if (getTargets().isTargeting(o)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
SpellAbility p = getParent();
|
||||||
|
return p != null && p.isTargeting(o);
|
||||||
|
}
|
||||||
|
|
||||||
// Takes one argument like Permanent.Blue+withFlying
|
// Takes one argument like Permanent.Blue+withFlying
|
||||||
@Override
|
@Override
|
||||||
public final boolean isValid(final String restriction, final Player sourceController, final Card source, SpellAbility spellAbility) {
|
public final boolean isValid(final String restriction, final Player sourceController, final Card source, SpellAbility spellAbility) {
|
||||||
// Inclusive restrictions are Card types
|
// Inclusive restrictions are Card types
|
||||||
final String[] incR = restriction.split("\\.", 2);
|
final String[] incR = restriction.split("\\.", 2);
|
||||||
|
SpellAbility root = getRootAbility();
|
||||||
|
|
||||||
if (incR[0].equals("Spell")) {
|
if (incR[0].equals("Spell")) {
|
||||||
if (!isSpell()) {
|
if (!root.isSpell()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (incR[0].equals("Triggered")) {
|
else if (incR[0].equals("Triggered")) {
|
||||||
if (!isTrigger()) {
|
if (!root.isTrigger()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (incR[0].equals("Activated")) {
|
else if (incR[0].equals("Activated")) {
|
||||||
if (!(this instanceof AbilityActivated)) {
|
if (!(root instanceof AbilityActivated)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public class SimulateMatch {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static void simulateSingleMatch(Match mc, int iGame, boolean outputGamelog) {
|
private static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) {
|
||||||
final StopWatch sw = new StopWatch();
|
final StopWatch sw = new StopWatch();
|
||||||
sw.start();
|
sw.start();
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ Name:Treasure Map
|
|||||||
ManaCost:2
|
ManaCost:2
|
||||||
Types:Artifact
|
Types:Artifact
|
||||||
A:AB$ Scry | Cost$ 1 T | ScryNum$ 1 | SubAbility$ DBLandmark | SpellDescription$ Scry 1. Put a landmark counter on CARDNAME. Then if there are three or more landmark counters on it, remove those counters, transform CARDNAME, and create three colorless Treasure artifact tokens with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."
|
A:AB$ Scry | Cost$ 1 T | ScryNum$ 1 | SubAbility$ DBLandmark | SpellDescription$ Scry 1. Put a landmark counter on CARDNAME. Then if there are three or more landmark counters on it, remove those counters, transform CARDNAME, and create three colorless Treasure artifact tokens with "{T}, Sacrifice this artifact: Add one mana of any color to your mana pool."
|
||||||
SVar:DBLandmark:DB$ PutCounter | Defined$ Self | CounterType$ LANDMARK | CounterNum$ 1 | SubAbility$ DBStoreSVar
|
SVar:DBLandmark:DB$ PutCounter | Defined$ Self | CounterType$ LANDMARK | CounterNum$ 1 | SubAbility$ DBBranch
|
||||||
SVar:DBStoreSVar:DB$ StoreSVar | SVar$ FoundTreasure | Type$ Number | Expression$ 1 | ConditionCheckSVar$ XMarksTheSpot | ConditionSVarCompare$ GE1 | References$ XMarksTheSpot,FoundTreasure | SubAbility$ DBRemoveCtrs
|
SVar:DBBranch:DB$ Branch | BranchConditionSVar$ XMarksTheSpot | References$ XMarksTheSpot | TrueSubAbility$ DBRemoveCtrs
|
||||||
SVar:DBRemoveCtrs:DB$ RemoveCounter | Defined$ Self | CounterType$ LANDMARK | CounterNum$ 3 | ConditionCheckSVar$ FoundTreasure | ConditionSVarCompare$ GE1 | References$ FoundTreasure | SubAbility$ DBTreasureTokens
|
SVar:DBRemoveCtrs:DB$ RemoveCounter | Defined$ Self | CounterType$ LANDMARK | CounterNum$ All | SubAbility$ DBTransform
|
||||||
SVar:DBTreasureTokens:DB$ Token | TokenAmount$ 3 | TokenName$ Treasure | TokenTypes$ Artifact,Treasure | TokenOwner$ You | TokenColors$ Colorless | TokenImage$ c treasure | TokenAbilities$ ABTreasureMana | TokenAltImages$ c_treasure2,c_treasure3,c_treasure4 | ConditionCheckSVar$ FoundTreasure | ConditionSVarCompare$ GE1 | SubAbility$ DBTransform | References$ FoundTreasure
|
SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | SubAbility$ DBTreasureTokens
|
||||||
SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ FoundTreasure | ConditionSVarCompare$ GE1 | References$ FoundTreasure
|
SVar:DBTreasureTokens:DB$ Token | TokenAmount$ 3 | TokenName$ Treasure | TokenTypes$ Artifact,Treasure | TokenOwner$ You | TokenColors$ Colorless | TokenImage$ c treasure | TokenAbilities$ ABTreasureMana | TokenAltImages$ c_treasure2,c_treasure3,c_treasure4 | References$ ABTreasureMana
|
||||||
SVar:ABTreasureMana:AB$ Mana | Cost$ T Sac<1/CARDNAME> | Produced$ Any | Amount$ 1 | SpellDescription$ Add one mana of any color to your mana pool.
|
SVar:ABTreasureMana:AB$ Mana | Cost$ T Sac<1/CARDNAME> | Produced$ Any | Amount$ 1 | SpellDescription$ Add one mana of any color to your mana pool.
|
||||||
SVar:XMarksTheSpot:Count$Valid Card.Self+counters_GE3_LANDMARK
|
SVar:XMarksTheSpot:Count$Valid Card.Self+counters_GE3_LANDMARK
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | Execute$ DBInitSVar | Static$ True
|
|
||||||
SVar:DBInitSVar:DB$ StoreSVar | SVar$ FoundTreasure | Type$ Number | Expression$ 0 | References$ FoundTreasure
|
|
||||||
SVar:FoundTreasure:Number$0
|
|
||||||
AlternateMode:DoubleFaced
|
AlternateMode:DoubleFaced
|
||||||
DeckHas:Ability$Token
|
DeckHas:Ability$Token
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/treasure_map.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/treasure_map.jpg
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import java.util.Map;
|
|||||||
* Created by maustin on 09/05/2017.
|
* Created by maustin on 09/05/2017.
|
||||||
*/
|
*/
|
||||||
public class CommanderDeckGenerator extends DeckProxy implements Comparable<CommanderDeckGenerator> {
|
public class CommanderDeckGenerator extends DeckProxy implements Comparable<CommanderDeckGenerator> {
|
||||||
public static List<DeckProxy> getCommanderDecks(DeckFormat format, boolean isForAi, boolean isCardGen){
|
public static List<DeckProxy> getCommanderDecks(final DeckFormat format, boolean isForAi, boolean isCardGen){
|
||||||
ItemPool uniqueCards;
|
ItemPool uniqueCards;
|
||||||
if(isCardGen){
|
if(isCardGen){
|
||||||
uniqueCards = new ItemPool<PaperCard>(PaperCard.class);
|
uniqueCards = new ItemPool<PaperCard>(PaperCard.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user