mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-22 10:22:15 -07:00
Have various game events respect the new UUID attribute on instantiation.
This commit is contained in:
parent
90c4b223dd
commit
c9690d790c
12 changed files with 58 additions and 26 deletions
|
|
@ -299,7 +299,7 @@ InnerDecklistNode *DeckListModel::createNodeIfNeeded(const QString &name, InnerD
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, const QString &zoneName) const
|
DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, const QString &zoneName, const QString &uuid) const
|
||||||
{
|
{
|
||||||
InnerDecklistNode *zoneNode, *typeNode;
|
InnerDecklistNode *zoneNode, *typeNode;
|
||||||
CardInfoPtr info;
|
CardInfoPtr info;
|
||||||
|
|
@ -321,14 +321,17 @@ DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, cons
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dynamic_cast<DecklistModelCardNode *>(typeNode->findChild(cardName));
|
if (uuid.isEmpty()) {
|
||||||
|
return dynamic_cast<DecklistModelCardNode *>(typeNode->findChild(cardName));
|
||||||
|
}
|
||||||
|
return dynamic_cast<DecklistModelCardNode *>(typeNode->findChild(cardName, uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex DeckListModel::findCard(const QString &cardName, const QString &zoneName) const
|
QModelIndex DeckListModel::findCard(const QString &cardName, const QString &zoneName, const QString &uuid) const
|
||||||
{
|
{
|
||||||
DecklistModelCardNode *cardNode;
|
DecklistModelCardNode *cardNode;
|
||||||
|
|
||||||
cardNode = findCardNode(cardName, zoneName);
|
cardNode = findCardNode(cardName, zoneName, uuid);
|
||||||
if (!cardNode) {
|
if (!cardNode) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -336,10 +339,14 @@ QModelIndex DeckListModel::findCard(const QString &cardName, const QString &zone
|
||||||
return nodeToIndex(cardNode);
|
return nodeToIndex(cardNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex DeckListModel::addCard(const QString &cardName, const QString &zoneName, bool abAddAnyway)
|
QModelIndex DeckListModel::addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway)
|
||||||
{
|
{
|
||||||
CardInfoPtr cardInfo = CardDatabaseManager::getInstance()->getCard(cardName);
|
return addCard(cardName, CardDatabaseManager::getInstance()->getPreferredSetForCard(cardName), zoneName, abAddAnyway);
|
||||||
CardInfoPerSet cardInfoSet = CardDatabaseManager::getInstance()->getPreferredSetForCard(cardName);
|
}
|
||||||
|
|
||||||
|
QModelIndex DeckListModel::addCard(const QString &cardName, const CardInfoPerSet cardInfoSet, const QString &zoneName, bool abAddAnyway)
|
||||||
|
{
|
||||||
|
CardInfoPtr cardInfo = CardDatabaseManager::getInstance()->getCardByNameAndUUID(cardName, cardInfoSet.getProperty("uuid"));
|
||||||
|
|
||||||
if (cardInfo == nullptr) {
|
if (cardInfo == nullptr) {
|
||||||
if (abAddAnyway) {
|
if (abAddAnyway) {
|
||||||
|
|
@ -359,7 +366,7 @@ QModelIndex DeckListModel::addCard(const QString &cardName, const QString &zoneN
|
||||||
InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode);
|
InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode);
|
||||||
|
|
||||||
const QModelIndex parentIndex = nodeToIndex(cardTypeNode);
|
const QModelIndex parentIndex = nodeToIndex(cardTypeNode);
|
||||||
auto *cardNode = dynamic_cast<DecklistModelCardNode *>(cardTypeNode->findChild(cardName));
|
auto *cardNode = dynamic_cast<DecklistModelCardNode *>(cardTypeNode->findChild(cardName, cardInfoSet.getProperty("uuid")));
|
||||||
if (!cardNode) {
|
if (!cardNode) {
|
||||||
auto *decklistCard = deckList->addCard(
|
auto *decklistCard = deckList->addCard(
|
||||||
cardInfo->getName(), zoneName, cardInfoSet.getProperty("uuid"), cardInfoSet.getProperty("num"));
|
cardInfo->getName(), zoneName, cardInfoSet.getProperty("uuid"), cardInfoSet.getProperty("num"));
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#define DECKLISTMODEL_H
|
#define DECKLISTMODEL_H
|
||||||
|
|
||||||
#include "decklist.h"
|
#include "decklist.h"
|
||||||
|
#include "../game/cards/card_database.h"
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
@ -81,8 +82,10 @@ public:
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||||
bool removeRows(int row, int count, const QModelIndex &parent) override;
|
bool removeRows(int row, int count, const QModelIndex &parent) override;
|
||||||
QModelIndex findCard(const QString &cardName, const QString &zoneName) const;
|
QModelIndex findCard(const QString &cardName, const QString &zoneName, const QString &uuid = "") const;
|
||||||
QModelIndex addCard(const QString &cardName, const QString &zoneName, bool abAddAnyway = false);
|
QModelIndex addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway);
|
||||||
|
QModelIndex
|
||||||
|
addCard(const ::QString &cardName, CardInfoPerSet cardInfoSet, const QString &zoneName, bool abAddAnyway = false);
|
||||||
void sort(int column, Qt::SortOrder order) override;
|
void sort(int column, Qt::SortOrder order) override;
|
||||||
void cleanList();
|
void cleanList();
|
||||||
DeckLoader *getDeckList() const
|
DeckLoader *getDeckList() const
|
||||||
|
|
@ -98,7 +101,7 @@ private:
|
||||||
Qt::SortOrder lastKnownOrder;
|
Qt::SortOrder lastKnownOrder;
|
||||||
InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent);
|
InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent);
|
||||||
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
||||||
DecklistModelCardNode *findCardNode(const QString &cardName, const QString &zoneName) const;
|
DecklistModelCardNode *findCardNode(const QString &cardName, const QString &zoneName, const QString &uuid = "") const;
|
||||||
void emitRecursiveUpdates(const QModelIndex &index);
|
void emitRecursiveUpdates(const QModelIndex &index);
|
||||||
void sortHelper(InnerDecklistNode *node, Qt::SortOrder order);
|
void sortHelper(InnerDecklistNode *node, Qt::SortOrder order);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ void DeckViewScene::rebuildTree()
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||||
DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentZone->getName(), container);
|
DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentCard->getCardUuid(), currentZone->getName(), container);
|
||||||
container->addCard(newCard);
|
container->addCard(newCard);
|
||||||
emit newCardAdded(newCard);
|
emit newCardAdded(newCard);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include "abstract_card_item.h"
|
#include "abstract_card_item.h"
|
||||||
|
|
||||||
#include "../../client/ui/picture_loader.h"
|
#include "../../client/ui/picture_loader.h"
|
||||||
#include "../../main.h"
|
|
||||||
#include "../../settings/cache_settings.h"
|
#include "../../settings/cache_settings.h"
|
||||||
#include "../game_scene.h"
|
#include "../game_scene.h"
|
||||||
#include "card_database.h"
|
#include "card_database.h"
|
||||||
|
|
@ -13,7 +12,11 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
AbstractCardItem::AbstractCardItem(const QString &_name, const QString &_uuid, Player *_owner, int _id, QGraphicsItem *parent)
|
AbstractCardItem::AbstractCardItem(const QString &_name,
|
||||||
|
const QString &_uuid,
|
||||||
|
Player *_owner,
|
||||||
|
int _id,
|
||||||
|
QGraphicsItem *parent)
|
||||||
: ArrowTarget(_owner, parent), id(_id), name(_name), uuid(_uuid), tapped(false), facedown(false), tapAngle(0),
|
: ArrowTarget(_owner, parent), id(_id), name(_name), uuid(_uuid), tapped(false), facedown(false), tapAngle(0),
|
||||||
bgColor(Qt::transparent), isHovered(false), realZValue(0)
|
bgColor(Qt::transparent), isHovered(false), realZValue(0)
|
||||||
{
|
{
|
||||||
|
|
@ -50,7 +53,7 @@ void AbstractCardItem::pixmapUpdated()
|
||||||
|
|
||||||
void AbstractCardItem::cardInfoUpdated()
|
void AbstractCardItem::cardInfoUpdated()
|
||||||
{
|
{
|
||||||
info = CardDatabaseManager::getInstance()->getCard(name);
|
info = CardDatabaseManager::getInstance()->getCardByNameAndUUID(name, uuid);
|
||||||
|
|
||||||
if (!info && !name.isEmpty()) {
|
if (!info && !name.isEmpty()) {
|
||||||
QVariantHash properties = QVariantHash();
|
QVariantHash properties = QVariantHash();
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ CardItem::CardItem(Player *_owner,
|
||||||
bool _revealedCard,
|
bool _revealedCard,
|
||||||
QGraphicsItem *parent,
|
QGraphicsItem *parent,
|
||||||
CardZone *_zone)
|
CardZone *_zone)
|
||||||
: AbstractCardItem(_name, _uuid, _owner, _cardid, parent), zone(_zone), revealedCard(_revealedCard), attacking(false),
|
: AbstractCardItem(_name, _uuid, _owner, _cardid, parent), zone(_zone), revealedCard(_revealedCard),
|
||||||
destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
|
attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
|
||||||
{
|
{
|
||||||
owner->addCard(this);
|
owner->addCard(this);
|
||||||
|
|
||||||
|
|
@ -243,6 +243,7 @@ void CardItem::processCardInfo(const ServerInfo_Card &_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
setId(_info.id());
|
setId(_info.id());
|
||||||
|
setUUID(QString::fromStdString(_info.uuid()));
|
||||||
setName(QString::fromStdString(_info.name()));
|
setName(QString::fromStdString(_info.name()));
|
||||||
setAttacking(_info.attacking());
|
setAttacking(_info.attacking());
|
||||||
setFaceDown(_info.face_down());
|
setFaceDown(_info.face_down());
|
||||||
|
|
|
||||||
|
|
@ -2031,7 +2031,7 @@ void Player::eventCreateToken(const Event_CreateToken &event)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardItem *card = new CardItem(this, QString::fromStdString(event.card_name()), event.card_id());
|
CardItem *card = new CardItem(this, QString::fromStdString(event.card_name()), QString(), event.card_id());
|
||||||
// use db PT if not provided in event
|
// use db PT if not provided in event
|
||||||
if (!QString::fromStdString(event.pt()).isEmpty()) {
|
if (!QString::fromStdString(event.pt()).isEmpty()) {
|
||||||
card->setPT(QString::fromStdString(event.pt()));
|
card->setPT(QString::fromStdString(event.pt()));
|
||||||
|
|
@ -2326,6 +2326,7 @@ void Player::eventDrawCards(const Event_DrawCards &event)
|
||||||
for (int i = 0; i < listSize; ++i) {
|
for (int i = 0; i < listSize; ++i) {
|
||||||
const ServerInfo_Card &cardInfo = event.cards(i);
|
const ServerInfo_Card &cardInfo = event.cards(i);
|
||||||
CardItem *card = _deck->takeCard(0, cardInfo.id());
|
CardItem *card = _deck->takeCard(0, cardInfo.id());
|
||||||
|
card->setUUID(QString::fromStdString(cardInfo.uuid()));
|
||||||
card->setName(QString::fromStdString(cardInfo.name()));
|
card->setName(QString::fromStdString(cardInfo.name()));
|
||||||
_hand->addCard(card, false, -1);
|
_hand->addCard(card, false, -1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ void CardZone::addCard(CardItem *card, bool reorganize, int x, int y)
|
||||||
{
|
{
|
||||||
for (auto *view : views) {
|
for (auto *view : views) {
|
||||||
if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
|
if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
|
||||||
view->addCard(new CardItem(player, card->getName(), card->getId()), reorganize, x, y);
|
view->addCard(new CardItem(player, card->getName(), card->getUUID(), card->getId()), reorganize, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,9 @@ void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardLis
|
||||||
{
|
{
|
||||||
if (!cardList.isEmpty()) {
|
if (!cardList.isEmpty()) {
|
||||||
for (int i = 0; i < cardList.size(); ++i)
|
for (int i = 0; i < cardList.size(); ++i)
|
||||||
addCard(
|
addCard(new CardItem(player, QString::fromStdString(cardList[i]->name()),
|
||||||
new CardItem(player, QString::fromStdString(cardList[i]->name()), cardList[i]->id(), revealZone, this),
|
QString::fromStdString(cardList[i]->uuid()), cardList[i]->id(), revealZone, this),
|
||||||
false, i);
|
false, i);
|
||||||
reorganizeCards();
|
reorganizeCards();
|
||||||
} else if (!origZone->contentsKnown()) {
|
} else if (!origZone->contentsKnown()) {
|
||||||
Command_DumpZone cmd;
|
Command_DumpZone cmd;
|
||||||
|
|
@ -75,7 +75,7 @@ void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardLis
|
||||||
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
|
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
|
||||||
for (int i = 0; i < number; i++) {
|
for (int i = 0; i < number; i++) {
|
||||||
CardItem *card = c.at(i);
|
CardItem *card = c.at(i);
|
||||||
addCard(new CardItem(player, card->getName(), card->getId(), revealZone, this), false, i);
|
addCard(new CardItem(player, card->getName(), card->getUUID(), card->getId(), revealZone, this), false, i);
|
||||||
}
|
}
|
||||||
reorganizeCards();
|
reorganizeCards();
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +88,8 @@ void ZoneViewZone::zoneDumpReceived(const Response &r)
|
||||||
for (int i = 0; i < respCardListSize; ++i) {
|
for (int i = 0; i < respCardListSize; ++i) {
|
||||||
const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i);
|
const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i);
|
||||||
auto cardName = QString::fromStdString(cardInfo.name());
|
auto cardName = QString::fromStdString(cardInfo.name());
|
||||||
auto *card = new CardItem(player, cardName, cardInfo.id(), revealZone, this, this);
|
auto cardUuid = QString::fromStdString(cardInfo.uuid());
|
||||||
|
auto *card = new CardItem(player, cardName, cardUuid, cardInfo.id(), revealZone, this, this);
|
||||||
cards.insert(i, card);
|
cards.insert(i, card);
|
||||||
}
|
}
|
||||||
reorganizeCards();
|
reorganizeCards();
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,16 @@ AbstractDecklistNode *InnerDecklistNode::findChild(const QString &_name)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AbstractDecklistNode *InnerDecklistNode::findChild(const QString &_name, const QString &_uuid)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < size(); i++) {
|
||||||
|
if (at(i)->getName() == _name && at(i)->getCardUuid() == _uuid) {
|
||||||
|
return at(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
int InnerDecklistNode::height() const
|
int InnerDecklistNode::height() const
|
||||||
{
|
{
|
||||||
return at(0)->height() + 1;
|
return at(0)->height() + 1;
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ public:
|
||||||
|
|
||||||
void clearTree();
|
void clearTree();
|
||||||
AbstractDecklistNode *findChild(const QString &_name);
|
AbstractDecklistNode *findChild(const QString &_name);
|
||||||
|
AbstractDecklistNode *findChild(const QString &_name, const QString &_uuid);
|
||||||
int height() const override;
|
int height() const override;
|
||||||
int recursiveCount(bool countTotalCards = false) const;
|
int recursiveCount(bool countTotalCards = false) const;
|
||||||
bool compare(AbstractDecklistNode *other) const override;
|
bool compare(AbstractDecklistNode *other) const override;
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ void Server_Card::getInfo(ServerInfo_Card *info)
|
||||||
QString displayedName = facedown ? QString() : name;
|
QString displayedName = facedown ? QString() : name;
|
||||||
|
|
||||||
info->set_id(id);
|
info->set_id(id);
|
||||||
|
info->set_uuid(uuid.toStdString());
|
||||||
info->set_name(displayedName.toStdString());
|
info->set_name(displayedName.toStdString());
|
||||||
info->set_x(coord_x);
|
info->set_x(coord_x);
|
||||||
info->set_y(coord_y);
|
info->set_y(coord_y);
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,8 @@ void Server_Player::setupZones()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||||
z->insertCard(new Server_Card(currentCard->getName(), nextCardId++, 0, 0, z), -1, 0);
|
z->insertCard(
|
||||||
|
new Server_Card(currentCard->getName(), currentCard->getCardUuid(), nextCardId++, 0, 0, z), -1, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -338,6 +339,7 @@ Response::ResponseCode Server_Player::drawCards(GameEventStorage &ges, int numbe
|
||||||
ServerInfo_Card *cardInfo = eventPrivate.add_cards();
|
ServerInfo_Card *cardInfo = eventPrivate.add_cards();
|
||||||
cardInfo->set_id(card->getId());
|
cardInfo->set_id(card->getId());
|
||||||
cardInfo->set_name(card->getName().toStdString());
|
cardInfo->set_name(card->getName().toStdString());
|
||||||
|
cardInfo->set_uuid(card->getUUID().toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
|
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
|
||||||
|
|
@ -1406,7 +1408,7 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer
|
||||||
yCoord = 0;
|
yCoord = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *card = new Server_Card(cardName, newCardId(), xCoord, yCoord);
|
auto *card = new Server_Card(cardName, QString(), newCardId(), xCoord, yCoord);
|
||||||
card->moveToThread(thread());
|
card->moveToThread(thread());
|
||||||
card->setPT(nameFromStdString(cmd.pt()));
|
card->setPT(nameFromStdString(cmd.pt()));
|
||||||
card->setColor(nameFromStdString(cmd.color()));
|
card->setColor(nameFromStdString(cmd.color()));
|
||||||
|
|
@ -1941,6 +1943,7 @@ Server_Player::cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, G
|
||||||
Server_Card *card = cards[i];
|
Server_Card *card = cards[i];
|
||||||
QString displayedName = card->getFaceDown() ? QString() : card->getName();
|
QString displayedName = card->getFaceDown() ? QString() : card->getName();
|
||||||
ServerInfo_Card *cardInfo = zoneInfo->add_card_list();
|
ServerInfo_Card *cardInfo = zoneInfo->add_card_list();
|
||||||
|
cardInfo->set_uuid(card->getUUID().toStdString());
|
||||||
cardInfo->set_name(displayedName.toStdString());
|
cardInfo->set_name(displayedName.toStdString());
|
||||||
if (zone->getType() == ServerInfo_Zone::HiddenZone) {
|
if (zone->getType() == ServerInfo_Zone::HiddenZone) {
|
||||||
cardInfo->set_id(i);
|
cardInfo->set_id(i);
|
||||||
|
|
@ -2058,6 +2061,7 @@ Server_Player::cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer
|
||||||
ServerInfo_Card *cardInfo = eventPrivate.add_cards();
|
ServerInfo_Card *cardInfo = eventPrivate.add_cards();
|
||||||
|
|
||||||
cardInfo->set_id(card->getId());
|
cardInfo->set_id(card->getId());
|
||||||
|
cardInfo->set_uuid(card->getUUID().toStdString());
|
||||||
cardInfo->set_name(card->getName().toStdString());
|
cardInfo->set_name(card->getName().toStdString());
|
||||||
cardInfo->set_x(card->getX());
|
cardInfo->set_x(card->getX());
|
||||||
cardInfo->set_y(card->getY());
|
cardInfo->set_y(card->getY());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue