add phase to delete arrows in to protocol (#6159)

* protocol changes

* servatrice changes

* add new setting

* implement client side with static 4 phases

* reading the code explains the code

* add subphases to phase.cpp

* use new subphase definition
This commit is contained in:
ebbit1q
2025-11-26 15:16:10 +01:00
committed by GitHub
parent adee67115c
commit a21e45ed36
16 changed files with 133 additions and 39 deletions

View File

@@ -261,6 +261,7 @@ SettingsCache::SettingsCache()
doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool();
clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool();
playToStack = settings->value("interface/playtostack", true).toBool();
doNotDeleteArrowsInSubPhases = settings->value("interface/doNotDeleteArrowsInSubPhases", true).toBool();
startingHandSize = settings->value("interface/startinghandsize", 7).toInt();
annotateTokens = settings->value("interface/annotatetokens", false).toBool();
tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray();
@@ -664,6 +665,12 @@ void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack)
settings->setValue("interface/playtostack", playToStack);
}
void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases)
{
doNotDeleteArrowsInSubPhases = static_cast<bool>(_doNotDeleteArrowsInSubPhases);
settings->setValue("interface/doNotDeleteArrowsInSubPhases", doNotDeleteArrowsInSubPhases);
}
void SettingsCache::setStartingHandSize(int _startingHandSize)
{
startingHandSize = _startingHandSize;

View File

@@ -228,6 +228,7 @@ private:
bool doubleClickToPlay;
bool clickPlaysAllSelected;
bool playToStack;
bool doNotDeleteArrowsInSubPhases;
int startingHandSize;
bool annotateTokens;
QByteArray tabGameSplitterSizes;
@@ -525,6 +526,10 @@ public:
{
return playToStack;
}
bool getDoNotDeleteArrowsInSubPhases() const
{
return doNotDeleteArrowsInSubPhases;
}
int getStartingHandSize() const
{
return startingHandSize;
@@ -984,6 +989,7 @@ public slots:
void setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay);
void setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected);
void setPlayToStack(QT_STATE_CHANGED_T _playToStack);
void setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases);
void setStartingHandSize(int _startingHandSize);
void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens);
void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes);

View File

@@ -151,8 +151,8 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
}
}
ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color)
: ArrowItem(_owner, -1, _startItem, 0, _color)
ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase)
{
}
@@ -231,20 +231,28 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
} else {
} else { // failed to cast target to card, this means it's a player
PlayerTarget *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem);
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
}
if (startZone->getName().compare("hand") == 0) {
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
if (startZone->getName() == "hand") {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow == 3) ||
(SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName().toStdString() != "stack")))
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci &&
((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName() != "stack")))
cmd.set_start_zone("stack");
else
cmd.set_start_zone(SettingsCache::instance().getPlayToStack() ? "stack" : "table");
cmd.set_start_zone(playToStack ? "stack" : "table");
}
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
player->getPlayerActions()->sendGameCommand(cmd);
}
delArrow();

View File

@@ -82,10 +82,11 @@ class ArrowDragItem : public ArrowItem
{
Q_OBJECT
private:
int deleteInPhase;
QList<ArrowDragItem *> childArrows;
public:
ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color);
ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *childArrow);
protected:

View File

@@ -3,6 +3,7 @@
#include "../../client/settings/cache_settings.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h"
#include "../phase.h"
#include "../player/player.h"
#include "../zones/card_zone.h"
#include "../zones/logic/view_zone_logic.h"
@@ -275,9 +276,14 @@ void CardItem::drawArrow(const QColor &arrowColor)
if (owner->getGame()->getPlayerManager()->isSpectator())
return;
Player *arrowOwner =
owner->getGame()->getPlayerManager()->getActiveLocalPlayer(owner->getGame()->getGameState()->getActivePlayer());
ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor);
auto *game = owner->getGame();
Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
int phase = 0; // 0 means to not set the phase
if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) {
int currentPhase = game->getGameState()->getCurrentPhase();
phase = Phases::getLastSubphase(currentPhase) + 1;
}
ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor, phase);
scene()->addItem(arrow);
arrow->grabMouse();
@@ -288,7 +294,7 @@ void CardItem::drawArrow(const QColor &arrowColor)
if (card->getZone() != zone)
continue;
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor);
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase);
scene()->addItem(childArrow);
arrow->addChildArrow(childArrow);
}

View File

@@ -22,6 +22,28 @@ Phase Phases::getPhase(int phase)
}
}
int Phases::getLastSubphase(int phase)
{
if (0 <= phase && phase < Phases::phaseTypesCount) {
return subPhasesEnd[phase];
} else {
return phase;
}
}
QVector<int> getSubPhasesEnd()
{
QVector<int> array(Phases::phaseTypesCount);
for (int phaseEnd = Phases::phaseTypesCount - 1; phaseEnd >= 0;) {
int subPhase = phaseEnd;
for (; subPhase >= 0 && Phases::phases[phaseEnd].color == Phases::phases[subPhase].color; --subPhase) {
array[subPhase] = phaseEnd;
}
phaseEnd = subPhase;
}
return array;
}
const Phase Phases::unknownPhase(QT_TRANSLATE_NOOP("Phase", "Unknown Phase"), "black", "unknown_phase");
const Phase Phases::phases[Phases::phaseTypesCount] = {
{QT_TRANSLATE_NOOP("Phase", "Untap"), "green", "untap_step"},
@@ -35,3 +57,4 @@ const Phase Phases::phases[Phases::phaseTypesCount] = {
{QT_TRANSLATE_NOOP("Phase", "End of Combat"), "red", "end_combat"},
{QT_TRANSLATE_NOOP("Phase", "Second Main"), "blue", "main_2"},
{QT_TRANSLATE_NOOP("Phase", "End/Cleanup"), "green", "end_step"}};
const QVector<int> Phases::subPhasesEnd = getSubPhasesEnd();

View File

@@ -28,8 +28,10 @@ struct Phases
const static int phaseTypesCount = 11;
const static Phase unknownPhase;
const static Phase phases[phaseTypesCount];
const static QVector<int> subPhasesEnd;
static Phase getPhase(int);
static int getLastSubphase(int phase);
};
#endif // PHASE_H

View File

@@ -801,6 +801,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setPlayToStack);
doNotDeleteArrowsInSubPhasesCheckBox.setChecked(SettingsCache::instance().getDoNotDeleteArrowsInSubPhases());
connect(&doNotDeleteArrowsInSubPhasesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setDoNotDeleteArrowsInSubPhases);
closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView());
connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setCloseEmptyCardView);
@@ -821,10 +825,11 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0);
generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0);
generalGrid->addWidget(&playToStackCheckBox, 2, 0);
generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0);
generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 4, 0);
generalGrid->addWidget(&annotateTokensCheckBox, 5, 0);
generalGrid->addWidget(&useTearOffMenusCheckBox, 6, 0);
generalGrid->addWidget(&doNotDeleteArrowsInSubPhasesCheckBox, 3, 0);
generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0);
generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0);
generalGrid->addWidget(&annotateTokensCheckBox, 6, 0);
generalGrid->addWidget(&useTearOffMenusCheckBox, 7, 0);
generalGroupBox = new QGroupBox;
generalGroupBox->setLayout(generalGrid);
@@ -942,6 +947,7 @@ void UserInterfaceSettingsPage::retranslateUi()
doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)"));
clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)"));
playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default"));
doNotDeleteArrowsInSubPhasesCheckBox.setText(tr("Do not delete &arrows inside of subphases"));
closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed"));
focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened"));
annotateTokensCheckBox.setText(tr("Annotate card text on tokens"));

View File

@@ -163,6 +163,7 @@ private:
QCheckBox doubleClickToPlayCheckBox;
QCheckBox clickPlaysAllSelectedCheckBox;
QCheckBox playToStackCheckBox;
QCheckBox doNotDeleteArrowsInSubPhasesCheckBox;
QCheckBox closeEmptyCardViewCheckBox;
QCheckBox focusCardViewSearchBarCheckBox;
QCheckBox annotateTokensCheckBox;

View File

@@ -182,6 +182,9 @@ void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAl
void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */)
{
}
void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T /* _doNotDeleteArrowsInSubPhases */)
{
}
void SettingsCache::setStartingHandSize(int /* _startingHandSize */)
{
}
@@ -445,4 +448,4 @@ void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */)
void CardPictureLoader::clearPixmapCache(CardInfoPtr /* card */)
{
}
}

View File

@@ -1210,7 +1210,9 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo
}
}
auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color());
int currentPhase = game->getActivePhase();
int deletionPhase = cmd.has_delete_in_phase() ? cmd.delete_in_phase() : currentPhase;
auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase);
addArrow(arrow);
Event_CreateArrow event;

View File

@@ -6,8 +6,14 @@
#include <libcockatrice/protocol/pb/serverinfo_arrow.pb.h>
Server_Arrow::Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor)
: id(_id), startCard(_startCard), targetItem(_targetItem), arrowColor(_arrowColor)
Server_Arrow::Server_Arrow(int _id,
Server_Card *_startCard,
Server_ArrowTarget *_targetItem,
const color &_arrowColor,
int _phaseCreated,
int _phaseDeleted)
: id(_id), startCard(_startCard), targetItem(_targetItem), arrowColor(_arrowColor), phaseCreated(_phaseCreated),
phaseDeleted(_phaseDeleted)
{
}

View File

@@ -14,9 +14,15 @@ private:
Server_Card *startCard;
Server_ArrowTarget *targetItem;
color arrowColor;
int phaseCreated, phaseDeleted;
public:
Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor);
Server_Arrow(int _id,
Server_Card *_startCard,
Server_ArrowTarget *_targetItem,
const color &_arrowColor,
int _phaseCreated,
int _phaseDeleted);
int getId() const
{
return id;
@@ -45,6 +51,10 @@ public:
{
return arrowColor;
}
bool checkPhaseDeletion(int phase) const // returns true if the arrow should be deleted in this phase
{
return phase < phaseCreated || phase >= phaseDeleted;
}
void getInfo(ServerInfo_Arrow *info);
};

View File

@@ -654,6 +654,8 @@ void Server_Game::setActivePlayer(int _activePlayer)
{
QMutexLocker locker(&gameMutex);
removeArrows(0, true);
activePlayer = _activePlayer;
Event_SetActivePlayer event;
@@ -663,30 +665,35 @@ void Server_Game::setActivePlayer(int _activePlayer)
setActivePhase(0);
}
void Server_Game::setActivePhase(int _activePhase)
void Server_Game::setActivePhase(int newPhase)
{
QMutexLocker locker(&gameMutex);
for (auto *player : getPlayers().values()) {
QList<Server_Arrow *> toDelete = player->getArrows().values();
for (int i = 0; i < toDelete.size(); ++i) {
Server_Arrow *a = toDelete[i];
Event_DeleteArrow event;
event.set_arrow_id(a->getId());
sendGameEventContainer(prepareGameEvent(event, player->getPlayerId()));
player->deleteArrow(a->getId());
}
}
activePhase = _activePhase;
removeArrows(newPhase);
activePhase = newPhase;
Event_SetActivePhase event;
event.set_phase(activePhase);
sendGameEventContainer(prepareGameEvent(event, -1));
}
void Server_Game::removeArrows(int newPhase, bool force)
{
QMutexLocker locker(&gameMutex);
for (auto *anyPlayer : getPlayers().values()) {
for (auto *arrowToDelete : anyPlayer->getArrows().values()) { // values creates a copy
if (force || arrowToDelete->checkPhaseDeletion(newPhase)) {
Event_DeleteArrow event;
event.set_arrow_id(arrowToDelete->getId());
sendGameEventContainer(prepareGameEvent(event, anyPlayer->getPlayerId()));
anyPlayer->deleteArrow(arrowToDelete->getId());
}
}
}
}
void Server_Game::nextTurn()
{
QMutexLocker locker(&gameMutex);

View File

@@ -198,8 +198,9 @@ public:
{
return activePhase;
}
void setActivePlayer(int _activePlayer);
void setActivePhase(int _activePhase);
void setActivePlayer(int newPlayer);
void setActivePhase(int newPhase);
void removeArrows(int newPhase, bool force = false);
void nextTurn();
int getSecondsElapsed() const
{

View File

@@ -27,4 +27,9 @@ message Command_CreateArrow {
// the color of the arrow
optional color arrow_color = 7;
// the phase that this arrow is deleted in, arrows are deleted when this or a later phase is activated and also
// when the phase moves back before the current phase or the turn is passed, when not set the arrow is deleted
// immediately when the phase is changed
optional sint32 delete_in_phase = 8;
}