mirror of
https://github.com/correl/mage.git
synced 2025-03-12 17:00:08 -09:00
Many changes to split cards handling (showing arrows and log text for fused spells., handling protection and other things correctly).
This commit is contained in:
parent
22bdb209ab
commit
101194acf7
11 changed files with 165 additions and 98 deletions
|
@ -14,6 +14,7 @@ import javax.swing.*;
|
|||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import mage.abilities.SpellAbility;
|
||||
|
||||
/**
|
||||
* Card info pane for displaying card rules.
|
||||
|
|
|
@ -46,6 +46,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.counters.Counter;
|
||||
|
@ -106,24 +107,17 @@ public class CardView extends SimpleCardView {
|
|||
fillEmpty();
|
||||
return;
|
||||
}
|
||||
Card cardHalf = null;
|
||||
|
||||
SplitCard splitCard = null;
|
||||
this.isSplitCard = card.isSplitCard();
|
||||
if (card instanceof Spell) {
|
||||
if (((Spell) card).getSpellAbility().getSpellAbilityType().equals(Constants.SpellAbilityType.SPLIT_LEFT)) {
|
||||
splitCard = (SplitCard) ((Spell) card).getCard();
|
||||
cardHalf = ((SplitCard) splitCard).getLeftHalfCard();
|
||||
} else if (((Spell) card).getSpellAbility().getSpellAbilityType().equals(Constants.SpellAbilityType.SPLIT_RIGHT)) {
|
||||
splitCard = (SplitCard) ((Spell) card).getCard();
|
||||
cardHalf = ((SplitCard) splitCard).getRightHalfCard();
|
||||
} else if (((Spell) card).getSpellAbility().getSpellAbilityType().equals(Constants.SpellAbilityType.SPLIT_FUSED)) {
|
||||
isSplitCard = true;
|
||||
if (card.isSplitCard()) {
|
||||
splitCard = (SplitCard) card;
|
||||
} else {
|
||||
if (card instanceof Spell && ((Spell) card).getSpellAbility().getSpellAbilityType().equals(Constants.SpellAbilityType.SPLIT_FUSED)) {
|
||||
splitCard = (SplitCard) ((Spell) card).getCard();
|
||||
}
|
||||
} else if (card.isSplitCard()) {
|
||||
splitCard = (SplitCard) card;
|
||||
}
|
||||
if (this.isSplitCard && splitCard != null) {
|
||||
if (splitCard != null) {
|
||||
this.isSplitCard = true;
|
||||
leftSplitName = splitCard.getLeftHalfCard().getName();
|
||||
leftSplitCosts = splitCard.getLeftHalfCard().getManaCost();
|
||||
leftSplitRules = splitCard.getLeftHalfCard().getRules();
|
||||
|
@ -132,18 +126,12 @@ public class CardView extends SimpleCardView {
|
|||
rightSplitRules = splitCard.getRightHalfCard().getRules();
|
||||
}
|
||||
|
||||
this.name = card.getName();
|
||||
if (cardHalf != null) {
|
||||
this.displayName = cardHalf.getName();
|
||||
this.rules = cardHalf.getRules();
|
||||
this.manaCost = cardHalf.getManaCost().getSymbols();
|
||||
this.convertedManaCost = cardHalf.getManaCost().convertedManaCost();
|
||||
} else {
|
||||
this.displayName = card.getName();
|
||||
this.rules = card.getRules();
|
||||
this.manaCost = card.getManaCost().getSymbols();
|
||||
this.convertedManaCost = card.getManaCost().convertedManaCost();
|
||||
}
|
||||
this.name = card.getImageName();
|
||||
this.displayName = card.getName();
|
||||
this.rules = card.getRules();
|
||||
this.manaCost = card.getManaCost().getSymbols();
|
||||
this.convertedManaCost = card.getManaCost().convertedManaCost();
|
||||
|
||||
if (card instanceof Permanent) {
|
||||
Permanent permanent = (Permanent)card;
|
||||
this.power = Integer.toString(card.getPower().getValue());
|
||||
|
@ -182,8 +170,10 @@ public class CardView extends SimpleCardView {
|
|||
|
||||
if (card instanceof Spell) {
|
||||
Spell<?> spell = (Spell<?>) card;
|
||||
if (spell.getSpellAbility().getTargets().size() > 0) {
|
||||
setTargets(spell.getSpellAbility().getTargets());
|
||||
for (SpellAbility spellAbility: spell.getSpellAbilities()) {
|
||||
if (spellAbility.getTargets().size() > 0) {
|
||||
setTargets(spellAbility.getTargets());
|
||||
}
|
||||
}
|
||||
// show for modal spell, which mode was choosen
|
||||
if (spell.getSpellAbility().isModal()) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.List;
|
|||
public interface MageObject extends MageItem, Serializable {
|
||||
|
||||
String getName();
|
||||
String getImageName();
|
||||
void setName(String name);
|
||||
|
||||
List<CardType> getCardType();
|
||||
|
|
|
@ -98,6 +98,11 @@ public abstract class MageObjectImpl<T extends MageObjectImpl<T>> implements Mag
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
|
|
|
@ -34,6 +34,7 @@ import mage.Constants.AbilityType;
|
|||
import mage.Constants.TimingRule;
|
||||
import mage.Constants.Zone;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.common.SpellCastTriggeredAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.Costs;
|
||||
import mage.abilities.costs.OptionalAdditionalSourceCosts;
|
||||
|
@ -240,10 +241,30 @@ public abstract class ActivatedAbilityImpl<T extends ActivatedAbilityImpl<T>> ex
|
|||
} else {
|
||||
sb.append("unknown");
|
||||
}
|
||||
if (getTargets().size() > 0) {
|
||||
sb.append(" targeting ");
|
||||
for (Target target: getTargets()) {
|
||||
sb.append(target.getTargetedName(game));
|
||||
if (object instanceof Spell && ((Spell) object).getSpellAbility().getSpellAbilityType().equals(Constants.SpellAbilityType.SPLIT_FUSED)) {
|
||||
Spell<?> spell = (Spell<?>) object;
|
||||
int i = 0;
|
||||
for (SpellAbility spellAbility : spell.getSpellAbilities()) {
|
||||
i++;
|
||||
String half;
|
||||
if (i == 1) {
|
||||
half = " left";
|
||||
} else {
|
||||
half = " right";
|
||||
}
|
||||
if (spellAbility.getTargets().size() > 0) {
|
||||
sb.append(half).append(" half targeting ");
|
||||
for (Target target: spellAbility.getTargets()) {
|
||||
sb.append(target.getTargetedName(game));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (getTargets().size() > 0) {
|
||||
sb.append(" targeting ");
|
||||
for (Target target: getTargets()) {
|
||||
sb.append(target.getTargetedName(game));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
|
|
|
@ -39,6 +39,7 @@ import mage.game.Game;
|
|||
|
||||
import java.util.UUID;
|
||||
import mage.Constants.SpellAbilityType;
|
||||
import mage.cards.SplitCard;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -98,8 +99,18 @@ public class SpellAbility extends ActivatedAbilityImpl<SpellAbility> {
|
|||
if (this.getManaCosts().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (costs.canPay(sourceId, controllerId, game) && canChooseTarget(game)) {
|
||||
return true;
|
||||
if (costs.canPay(sourceId, controllerId, game)) {
|
||||
if (getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) {
|
||||
SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
|
||||
if (splitCard != null) {
|
||||
return (splitCard.getLeftHalfCard().getSpellAbility().canChooseTarget(game)
|
||||
&& splitCard.getRightHalfCard().getSpellAbility().canChooseTarget(game));
|
||||
}
|
||||
return false;
|
||||
|
||||
} else {
|
||||
return canChooseTarget(game);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -31,6 +31,7 @@ package mage.cards;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.Constants;
|
||||
import mage.Constants.CardType;
|
||||
import mage.Constants.Rarity;
|
||||
import mage.Constants.SpellAbilityType;
|
||||
|
@ -38,6 +39,7 @@ import mage.abilities.Abilities;
|
|||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.game.Game;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
/**
|
||||
|
@ -66,16 +68,16 @@ public abstract class SplitCard<T extends SplitCard<T>> extends CardImpl<T> {
|
|||
private Card createLeftHalfCard (String nameLeft, String costsLeft) {
|
||||
CardType[] cardTypes = new CardType[getCardType().size()];
|
||||
this.getCardType().toArray(cardTypes);
|
||||
leftHalfCard = new LeftHalfCard(this.getOwnerId(), this.getCardNumber(), nameLeft, this.rarity, cardTypes, costsLeft);
|
||||
leftHalfCard.getAbilities().setSourceId(objectId);
|
||||
leftHalfCard = new LeftHalfCard(this.getOwnerId(), this.getCardNumber(), nameLeft, this.rarity, cardTypes, costsLeft, this);
|
||||
//leftHalfCard.getAbilities().setSourceId(objectId);
|
||||
return leftHalfCard;
|
||||
}
|
||||
|
||||
private Card createRightHalfCard (String nameRight, String costsRight) {
|
||||
CardType[] cardTypes = new CardType[getCardType().size()];
|
||||
this.getCardType().toArray(cardTypes);
|
||||
rightHalfCard = new RightHalfCard(this.getOwnerId(), this.getCardNumber(), nameRight, this.rarity, cardTypes, costsRight);
|
||||
rightHalfCard.getAbilities().setSourceId(objectId);
|
||||
rightHalfCard = new RightHalfCard(this.getOwnerId(), this.getCardNumber(), nameRight, this.rarity, cardTypes, costsRight, this);
|
||||
//rightHalfCard.getAbilities().setSourceId(objectId);
|
||||
return rightHalfCard;
|
||||
}
|
||||
|
||||
|
@ -89,6 +91,18 @@ public abstract class SplitCard<T extends SplitCard<T>> extends CardImpl<T> {
|
|||
return rightHalfCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cast(Game game, Constants.Zone fromZone, SpellAbility ability, UUID controllerId) {
|
||||
switch(ability.getSpellAbilityType()) {
|
||||
case SPLIT_LEFT:
|
||||
return this.getLeftHalfCard().cast(game, fromZone, ability, controllerId);
|
||||
case SPLIT_RIGHT:
|
||||
return this.getRightHalfCard().cast(game, fromZone, ability, controllerId);
|
||||
default:
|
||||
return super.cast(game, fromZone, ability, controllerId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getAbilities(){
|
||||
Abilities<Ability> allAbilites = new AbilitiesImpl<Ability>();
|
||||
|
@ -97,7 +111,6 @@ public abstract class SplitCard<T extends SplitCard<T>> extends CardImpl<T> {
|
|||
allAbilites.add(ability);
|
||||
}
|
||||
}
|
||||
allAbilites.addAll(super.getAbilities());
|
||||
allAbilites.addAll(leftHalfCard.getAbilities());
|
||||
allAbilites.addAll(rightHalfCard.getAbilities());
|
||||
return allAbilites;
|
||||
|
@ -147,12 +160,16 @@ public abstract class SplitCard<T extends SplitCard<T>> extends CardImpl<T> {
|
|||
*/
|
||||
class LeftHalfCard extends CardImpl<LeftHalfCard> {
|
||||
|
||||
public LeftHalfCard(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs ) {
|
||||
SplitCard splitCardParent;
|
||||
|
||||
public LeftHalfCard(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs, SplitCard splitCardParent) {
|
||||
super(ownerId, cardNumber, name, rarity, cardTypes, costs, SpellAbilityType.SPLIT_LEFT);
|
||||
this.splitCardParent = splitCardParent;
|
||||
}
|
||||
|
||||
public LeftHalfCard(final LeftHalfCard card) {
|
||||
super(card);
|
||||
this.splitCardParent = card.splitCardParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -160,25 +177,20 @@ class LeftHalfCard extends CardImpl<LeftHalfCard> {
|
|||
return new LeftHalfCard(this);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public List<String> getRules() {
|
||||
// List<String> rules = new ArrayList<String>();
|
||||
// // TODO: Move formatting to client CardInfoPaneImpl.java
|
||||
// StringBuilder buffer = new StringBuilder();
|
||||
// buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%'>");
|
||||
// buffer.append("<tr><td valign='top'><b>");
|
||||
// buffer.append(this.getName());
|
||||
// buffer.append("</b></td><td align='right' valign='top' style='width:");
|
||||
// buffer.append(getSpellAbility().getManaCosts().getSymbols().size() * 11 + 1);
|
||||
// buffer.append("px'>");
|
||||
// buffer.append(getSpellAbility().getManaCosts().getText());
|
||||
// buffer.append("</td></tr></table>");
|
||||
//
|
||||
// rules.add(buffer.toString());
|
||||
// rules.addAll(super.getRules());
|
||||
// return rules;
|
||||
// }
|
||||
@Override
|
||||
public String getImageName() {
|
||||
return splitCardParent.getImageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpansionSetCode() {
|
||||
return splitCardParent.getExpansionSetCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCardNumber() {
|
||||
return splitCardParent.getCardNumber();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -187,12 +199,16 @@ class LeftHalfCard extends CardImpl<LeftHalfCard> {
|
|||
*/
|
||||
class RightHalfCard extends CardImpl<RightHalfCard> {
|
||||
|
||||
public RightHalfCard(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) {
|
||||
SplitCard splitCardParent;
|
||||
|
||||
public RightHalfCard(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs, SplitCard splitCardParent) {
|
||||
super(ownerId, cardNumber, name, rarity, cardTypes, costs, SpellAbilityType.SPLIT_RIGHT);
|
||||
this.splitCardParent = splitCardParent;
|
||||
}
|
||||
|
||||
public RightHalfCard(final RightHalfCard card) {
|
||||
super(card);
|
||||
this.splitCardParent = card.splitCardParent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -200,21 +216,19 @@ class RightHalfCard extends CardImpl<RightHalfCard> {
|
|||
return new RightHalfCard(this);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public List<String> getRules() {
|
||||
// List<String> rules = new ArrayList<String>();
|
||||
// // TODO: Move formatting to client CardInfoPaneImpl.java
|
||||
// StringBuilder buffer = new StringBuilder();
|
||||
// buffer.append("<table cellspacing=0 cellpadding=0 border=0 width='100%'>");
|
||||
// buffer.append("<tr><td valign='top'><b>");
|
||||
// buffer.append(this.getName());
|
||||
// buffer.append("</b></td><td align='right' valign='top' style='width:");
|
||||
// buffer.append(getSpellAbility().getManaCosts().getSymbols().size() * 11 + 1);
|
||||
// buffer.append("px'>");
|
||||
// buffer.append(getSpellAbility().getManaCosts().getText());
|
||||
// buffer.append("</td></tr></table>");
|
||||
// rules.add(buffer.toString());
|
||||
// rules.addAll(super.getRules());
|
||||
// return rules;
|
||||
// }
|
||||
@Override
|
||||
public String getImageName() {
|
||||
return splitCardParent.getImageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpansionSetCode() {
|
||||
return splitCardParent.getExpansionSetCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCardNumber() {
|
||||
return splitCardParent.getCardNumber();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ import java.io.IOException;
|
|||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
|
||||
|
||||
|
@ -234,6 +235,16 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
|
|||
card.setOwnerId(ownerId);
|
||||
gameCards.put(card.getId(), card);
|
||||
state.addCard(card);
|
||||
if (card.isSplitCard()) {
|
||||
Card leftCard = ((SplitCard)card).getLeftHalfCard();
|
||||
leftCard.setOwnerId(ownerId);
|
||||
gameCards.put(leftCard.getId(), leftCard);
|
||||
state.addCard(leftCard);
|
||||
Card rightCard = ((SplitCard)card).getRightHalfCard();
|
||||
rightCard.setOwnerId(ownerId);
|
||||
gameCards.put(rightCard.getId(), rightCard);
|
||||
state.addCard(rightCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1681,7 +1692,7 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
|
|||
case LIBRARY:
|
||||
if (command.getValue().equals("clear")) {
|
||||
for (UUID card : player.getLibrary().getCardList()) {
|
||||
gameCards.remove(card);
|
||||
removeCard(card);
|
||||
}
|
||||
player.getLibrary().clear();
|
||||
}
|
||||
|
@ -1710,11 +1721,20 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
|
|||
|
||||
private void removeCards(Cards cards) {
|
||||
for (UUID card : cards) {
|
||||
gameCards.remove(card);
|
||||
removeCard(card);
|
||||
}
|
||||
cards.clear();
|
||||
}
|
||||
|
||||
private void removeCard(UUID cardId) {
|
||||
Card card = this.getCard(cardId);
|
||||
if(card != null && card.isSplitCard()) {
|
||||
gameCards.remove(((SplitCard)card).getLeftHalfCard().getId());
|
||||
gameCards.remove(((SplitCard)card).getRightHalfCard().getId());
|
||||
}
|
||||
gameCards.remove(cardId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard) {
|
||||
Player player = getPlayer(ownerId);
|
||||
|
@ -1763,7 +1783,7 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
|
|||
Player player = getPlayer(ownerId);
|
||||
if (player != null) {
|
||||
for (UUID card : player.getLibrary().getCardList()) {
|
||||
gameCards.remove(card);
|
||||
removeCard(card);
|
||||
}
|
||||
player.getLibrary().clear();
|
||||
Set<Card> cards = new HashSet<Card>();
|
||||
|
|
|
@ -97,6 +97,11 @@ public class Emblem implements CommandObject {
|
|||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {}
|
||||
|
||||
|
|
|
@ -56,8 +56,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.Constants;
|
||||
import static mage.Constants.SpellAbilityType.SPLIT_LEFT;
|
||||
import static mage.Constants.SpellAbilityType.SPLIT_RIGHT;
|
||||
import mage.cards.SplitCard;
|
||||
|
||||
/**
|
||||
|
@ -139,7 +137,7 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
|
|||
result = false;
|
||||
boolean legalParts = false;
|
||||
for(SpellAbility spellAbility: this.spellAbilities) {
|
||||
if (spellAbility.getTargets().stillLegal(ability, game)) {
|
||||
if (spellAbility.getTargets().stillLegal(spellAbility, game)) {
|
||||
legalParts = true;
|
||||
updateOptionalCosts(index);
|
||||
result |= spellAbility.resolve(game);
|
||||
|
@ -270,6 +268,11 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
|
|||
return card.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageName() {
|
||||
return card.getImageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {}
|
||||
|
||||
|
@ -301,6 +304,11 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
|
|||
return card.getSupertype();
|
||||
}
|
||||
|
||||
|
||||
public List<SpellAbility> getSpellAbilities() {
|
||||
return spellAbilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getAbilities() {
|
||||
return card.getAbilities();
|
||||
|
@ -361,26 +369,12 @@ public class Spell<T extends Spell<T>> implements StackObject, Card {
|
|||
|
||||
@Override
|
||||
public List<String> getRules() {
|
||||
switch (ability.getSpellAbilityType()) {
|
||||
case SPLIT_LEFT:
|
||||
return ((SplitCard)card).getLeftHalfCard().getRules();
|
||||
case SPLIT_RIGHT:
|
||||
return ((SplitCard)card).getRightHalfCard().getRules();
|
||||
default:
|
||||
return card.getRules();
|
||||
}
|
||||
return card.getRules();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Watcher> getWatchers() {
|
||||
switch (ability.getSpellAbilityType()) {
|
||||
case SPLIT_LEFT:
|
||||
return ((SplitCard)card).getLeftHalfCard().getWatchers();
|
||||
case SPLIT_RIGHT:
|
||||
return ((SplitCard)card).getLeftHalfCard().getWatchers();
|
||||
default:
|
||||
return card.getWatchers();
|
||||
}
|
||||
return card.getWatchers();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -110,6 +110,11 @@ public class StackAbility implements StackObject, Ability {
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImageName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getExpansionSetCode() {
|
||||
return expansionSetCode;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue